mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-11-13 12:23:47 +01:00
Merge branch 'advplyr:master' into master
This commit is contained in:
33 files changed
+436
-91
No files matched your search
@@ -5,10 +5,7 @@ import com.audiobookshelf.app.MainActivity
|
||||
import com.audiobookshelf.app.device.DeviceManager
|
||||
import com.audiobookshelf.app.server.ApiHandler
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.getcapacitor.JSObject
|
||||
import com.getcapacitor.Plugin
|
||||
import com.getcapacitor.PluginCall
|
||||
import com.getcapacitor.PluginMethod
|
||||
import com.getcapacitor.*
|
||||
import com.getcapacitor.annotation.CapacitorPlugin
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@@ -202,6 +199,27 @@ class AbsDatabase : Plugin() {
|
||||
}
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun updateLocalTrackOrder(call:PluginCall) {
|
||||
var localLibraryItemId = call.getString("localLibraryItemId", "") ?: ""
|
||||
var localLibraryItem = DeviceManager.dbManager.getLocalLibraryItem(localLibraryItemId)
|
||||
if (localLibraryItem == null) {
|
||||
call.resolve()
|
||||
return
|
||||
}
|
||||
|
||||
var tracks:JSArray = call.getArray("tracks") ?: JSArray()
|
||||
Log.d(tag, "updateLocalTrackOrder $tracks")
|
||||
|
||||
for (i in 0..tracks.length()) {
|
||||
var track = tracks.getJSONObject(i)
|
||||
var localFileId = track.getString("localFileId")
|
||||
Log.d(tag, "LOCAL FILE ID $localFileId")
|
||||
}
|
||||
|
||||
call.resolve()
|
||||
}
|
||||
|
||||
//
|
||||
// Generic Webview calls to db
|
||||
//
|
||||
|
||||
@@ -100,8 +100,9 @@ class AbsDownloader : Plugin() {
|
||||
fun downloadLibraryItem(call: PluginCall) {
|
||||
var libraryItemId = call.data.getString("libraryItemId").toString()
|
||||
var episodeId = call.data.getString("episodeId").toString()
|
||||
if (episodeId == "null") episodeId = ""
|
||||
var localFolderId = call.data.getString("localFolderId").toString()
|
||||
Log.d(tag, "Download library item $libraryItemId to folder $localFolderId")
|
||||
Log.d(tag, "Download library item $libraryItemId to folder $localFolderId / episode: $episodeId")
|
||||
|
||||
var downloadId = if (episodeId.isNullOrEmpty()) libraryItemId else "$libraryItemId-$episodeId"
|
||||
if (downloadQueue.find { it.id == downloadId } != null) {
|
||||
|
||||
@@ -8,11 +8,9 @@
|
||||
<span class="material-icons text-3xl text-white">arrow_back</span>
|
||||
</a>
|
||||
<div v-if="user">
|
||||
<div class="px-4 py-2 bg-bg bg-opacity-30 rounded-md flex items-center" @click="clickShowLibraryModal">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
|
||||
</svg>
|
||||
<p class="text-base font-book leading-4 ml-2">{{ currentLibraryName }}</p>
|
||||
<div class="pl-3 pr-4 py-2 bg-bg bg-opacity-30 rounded-md flex items-center" @click="clickShowLibraryModal">
|
||||
<widgets-library-icon :icon="currentLibraryIcon" :size="4" />
|
||||
<p class="text-base font-book leading-4 ml-2 mt-0.5">{{ currentLibraryName }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
@@ -45,6 +43,9 @@ export default {
|
||||
currentLibraryName() {
|
||||
return this.currentLibrary ? this.currentLibrary.name : 'Main'
|
||||
},
|
||||
currentLibraryIcon() {
|
||||
return this.currentLibrary ? this.currentLibrary.icon : 'database'
|
||||
},
|
||||
showBack() {
|
||||
if (!this.$route.name) return true
|
||||
return this.$route.name !== 'index' && !this.$route.name.startsWith('bookshelf')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div @mouseover="mouseover" @mouseout="mouseout">
|
||||
<div>
|
||||
<div :style="{ width: width + 'px', height: height + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
||||
<!-- Image or placeholder -->
|
||||
<covers-author-image :author="author" />
|
||||
@@ -10,14 +10,6 @@
|
||||
<p class="text-center text-gray-200" :style="{ fontSize: sizeMultiplier * 0.65 + 'rem' }">{{ numBooks }} Book{{ numBooks === 1 ? '' : 's' }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Search icon btn -->
|
||||
<div v-show="!searching && isHovering" class="absolute top-0 left-0 p-2 cursor-pointer hover:text-white text-gray-200 transform transition-transform hover:scale-125" @click.prevent.stop="searchAuthor">
|
||||
<span class="material-icons text-lg">search</span>
|
||||
</div>
|
||||
<div v-show="isHovering && !searching" class="absolute top-0 right-0 p-2 cursor-pointer hover:text-white text-gray-200 transform transition-transform hover:scale-125" @click.prevent.stop="$emit('edit', author)">
|
||||
<span class="material-icons text-lg">edit</span>
|
||||
</div>
|
||||
|
||||
<!-- Loading spinner -->
|
||||
<div v-show="searching" class="absolute top-0 left-0 z-10 w-full h-full bg-black bg-opacity-50 flex items-center justify-center">
|
||||
<widgets-loading-spinner size="" />
|
||||
@@ -46,8 +38,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searching: false,
|
||||
isHovering: false
|
||||
searching: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -68,12 +59,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
mouseover() {
|
||||
this.isHovering = true
|
||||
},
|
||||
mouseout() {
|
||||
this.isHovering = false
|
||||
},
|
||||
async searchAuthor() {
|
||||
this.searching = true
|
||||
var response = await this.$axios.$post(`/api/authors/${this.authorId}/match`, { q: this.name }).catch((error) => {
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<template>
|
||||
<div class="flex h-full px-1 overflow-hidden shadow-sm">
|
||||
<!-- <img src="/icons/NoUserPhoto.png" class="w-40 h-40 max-h-40 object-contain" style="max-height: 40px; max-width: 40px" /> -->
|
||||
<div class="flex h-full px-1 overflow-hidden">
|
||||
<div class="overflow-hidden bg-primary rounded" style="height: 50px; width: 40px">
|
||||
<covers-author-image :author="author" />
|
||||
</div>
|
||||
<div class="flex-grow px-2 authorSearchCardContent h-full">
|
||||
<p class="truncate text-sm">{{ name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="flex h-full px-1 overflow-hidden shadow-sm">
|
||||
<div style="max-height: 48px; max-width: 48px" class="w-12 h-12 bg-primary overflow-hidden rounded">
|
||||
<svg width="140%" height="140%" style="margin-left: -20%; margin-top: -20%; opacity: 0.6" viewBox="0 0 177 266" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="white" d="M40.7156 165.47C10.2694 150.865 -31.5407 148.629 -38.0532 155.529L63.3191 204.159L76.9443 190.899C66.828 181.394 54.006 171.846 40.7156 165.47Z" stroke="white" stroke-width="4" transform="translate(-2 -1)" />
|
||||
@@ -19,18 +26,25 @@
|
||||
<div class="flex-grow px-2 authorSearchCardContent h-full">
|
||||
<p class="truncate text-sm">{{ author }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
author: String
|
||||
author: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
computed: {
|
||||
name() {
|
||||
return this.author.name
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<div class="flex h-full px-1 overflow-hidden">
|
||||
<covers-book-cover :audiobook="audiobook" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
<div class="flex-grow px-2 h-full audiobookSearchCardContent">
|
||||
<covers-book-cover :library-item="libraryItem" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
<div class="flex-grow px-2 audiobookSearchCardContent">
|
||||
<p v-if="matchKey !== 'title'" class="truncate text-sm">{{ title }}</p>
|
||||
<p v-else class="truncate text-sm" v-html="matchHtml" />
|
||||
|
||||
<p v-if="matchKey === 'subtitle'" class="truncate text-xs text-gray-300">{{ matchHtml }}</p>
|
||||
|
||||
<p v-if="matchKey !== 'authorFL'" class="text-xs text-gray-200 truncate">by {{ authorFL }}</p>
|
||||
<p v-if="matchKey !== 'authors'" class="text-xs text-gray-200 truncate">by {{ authorName }}</p>
|
||||
<p v-else class="truncate text-xs text-gray-200" v-html="matchHtml" />
|
||||
|
||||
<div v-if="matchKey === 'series' || matchKey === 'tags'" class="m-0 p-0 truncate" v-html="matchHtml" />
|
||||
<div v-if="matchKey === 'series' || matchKey === 'tags' || matchKey === 'isbn' || matchKey === 'asin'" class="m-0 p-0 truncate text-xs" v-html="matchHtml" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -18,7 +18,7 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
audiobook: {
|
||||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
@@ -37,17 +37,27 @@ export default {
|
||||
if (this.bookCoverAspectRatio === 1) return 50 * 1.2
|
||||
return 50
|
||||
},
|
||||
book() {
|
||||
return this.audiobook ? this.audiobook.book || {} : {}
|
||||
media() {
|
||||
return this.libraryItem ? this.libraryItem.media || {} : {}
|
||||
},
|
||||
mediaMetadata() {
|
||||
return this.media.metadata || {}
|
||||
},
|
||||
mediaType() {
|
||||
return this.libraryItem ? this.libraryItem.mediaType : null
|
||||
},
|
||||
isPodcast() {
|
||||
return this.mediaType === 'podcast'
|
||||
},
|
||||
title() {
|
||||
return this.book ? this.book.title : 'No Title'
|
||||
return this.mediaMetadata.title || 'No Title'
|
||||
},
|
||||
subtitle() {
|
||||
return this.book ? this.book.subtitle : ''
|
||||
return this.mediaMetadata.subtitle
|
||||
},
|
||||
authorFL() {
|
||||
return this.book ? this.book.authorFL : 'Unknown'
|
||||
authorName() {
|
||||
if (this.isPodcast) return this.mediaMetadata.author
|
||||
return this.mediaMetadata.authorName
|
||||
},
|
||||
matchHtml() {
|
||||
if (!this.matchText || !this.search) return ''
|
||||
@@ -69,7 +79,9 @@ export default {
|
||||
html += lastPart
|
||||
|
||||
if (this.matchKey === 'tags') return `<p class="truncate">Tags: ${html}</p>`
|
||||
if (this.matchKey === 'authorFL') return `by ${html}`
|
||||
if (this.matchKey === 'authors') return `by ${html}`
|
||||
if (this.matchKey === 'isbn') return `<p class="truncate">ISBN: ${html}</p>`
|
||||
if (this.matchKey === 'asin') return `<p class="truncate">ASIN: ${html}</p>`
|
||||
if (this.matchKey === 'series') return `<p class="truncate">Series: ${html}</p>`
|
||||
return `${html}`
|
||||
}
|
||||
@@ -42,11 +42,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Error widget -->
|
||||
<ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0 z-10">
|
||||
<div :style="{ height: 1.5 * sizeMultiplier + 'rem', width: 2.5 * sizeMultiplier + 'rem' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300">
|
||||
<span class="material-icons text-red-100 pr-1" :style="{ fontSize: 0.875 * sizeMultiplier + 'rem' }">priority_high</span>
|
||||
</div>
|
||||
</ui-tooltip>
|
||||
<div v-if="showError" :style="{ height: 1.5 * sizeMultiplier + 'rem', width: 2.5 * sizeMultiplier + 'rem' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300">
|
||||
<span class="material-icons text-red-100 pr-1" :style="{ fontSize: 0.875 * sizeMultiplier + 'rem' }">priority_high</span>
|
||||
</div>
|
||||
|
||||
<!-- Volume number -->
|
||||
<div v-if="seriesSequence && showSequence && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="absolute cover-bg" ref="coverBg" />
|
||||
</div>
|
||||
|
||||
<div class="w-full h-full absolute top-0 left-0">
|
||||
<div class="w-full h-full absolute top-0 left-0 bg-primary">
|
||||
<img v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<p class="whitespace-normal" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">
|
||||
{{ displayTitle }}<span v-if="seriesSequence"> #{{ seriesSequence }}</span>
|
||||
</p>
|
||||
<p class="truncate text-gray-400" :style="{ fontSize: 0.7 * sizeMultiplier + 'rem' }">{{ displayAuthor }}</p>
|
||||
<p class="truncate text-gray-400" :style="{ fontSize: 0.7 * sizeMultiplier + 'rem' }">by {{ displayAuthor }}</p>
|
||||
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.7 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -150,10 +150,11 @@ export default {
|
||||
return Math.max(2, 3 * this.sizeMultiplier)
|
||||
},
|
||||
author() {
|
||||
return this.mediaMetadata.authorName || ''
|
||||
if (this.isPodcast) return this.mediaMetadata.author || 'Unknown'
|
||||
return this.mediaMetadata.authorName || 'Unknown'
|
||||
},
|
||||
authorLF() {
|
||||
return this.mediaMetadata.authorNameLF || ''
|
||||
return this.mediaMetadata.authorNameLF || 'Unknown'
|
||||
},
|
||||
series() {
|
||||
// Only included when filtering by series or collapse series
|
||||
@@ -177,6 +178,7 @@ export default {
|
||||
return this.title
|
||||
},
|
||||
displayAuthor() {
|
||||
if (this.isPodcast) return this.author
|
||||
if (this.orderBy === 'media.metadata.authorNameLF') return this.authorLF
|
||||
return this.author
|
||||
},
|
||||
@@ -184,7 +186,6 @@ export default {
|
||||
if (this.orderBy === 'mtimeMs') return 'Modified ' + this.$formatDate(this._libraryItem.mtimeMs)
|
||||
if (this.orderBy === 'birthtimeMs') return 'Born ' + this.$formatDate(this._libraryItem.birthtimeMs)
|
||||
if (this.orderBy === 'addedAt') return 'Added ' + this.$formatDate(this._libraryItem.addedAt)
|
||||
if (this.orderBy === 'duration') return 'Duration: ' + this.$elapsedPrettyExtended(this.media.duration, false)
|
||||
if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size)
|
||||
return null
|
||||
},
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="flex h-full px-1 overflow-hidden">
|
||||
<covers-group-cover :name="series" :book-items="bookItems" :width="80" :height="60" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
<covers-group-cover :name="name" :book-items="bookItems" :width="80" :height="60" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
<div class="flex-grow px-2 seriesSearchCardContent h-full">
|
||||
<p class="truncate text-sm">{{ series }}</p>
|
||||
<p class="truncate text-sm">{{ name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -10,7 +10,10 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
series: String,
|
||||
series: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
bookItems: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
@@ -22,6 +25,9 @@ export default {
|
||||
computed: {
|
||||
bookCoverAspectRatio() {
|
||||
return this.$store.getters['getBookCoverAspectRatio']
|
||||
},
|
||||
name() {
|
||||
return this.series.name
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
|
||||
@@ -58,6 +58,7 @@ export default {
|
||||
return this.$route.query || {}
|
||||
},
|
||||
entityTitle() {
|
||||
if (this.isPodcast) return 'Podcasts'
|
||||
if (this.page === 'library') return 'Books'
|
||||
else if (this.page === 'series') {
|
||||
return 'Series'
|
||||
@@ -71,6 +72,9 @@ export default {
|
||||
return this.$store.state.globals.series.name
|
||||
}
|
||||
return null
|
||||
},
|
||||
isPodcast() {
|
||||
return this.$store.getters['libraries/getCurrentLibraryMediaType'] === 'podcast'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<svg fill="currentColor" class="h-full w-full p-px" viewBox="0 0 1978.03 2349.44">
|
||||
<path
|
||||
d="M2519.5,1438.39c-12.13-10.1-31-25-56.57-42.62V1197.31c0-505.94-410.15-916.09-916.1-916.09h0c-505.94,0-916.09,410.15-916.09,916.09v198.46c-25.57,17.66-44.44,32.52-56.57,42.62a45.45,45.45,0,0,0-16.35,34.95v237.74a45.45,45.45,0,0,0,16.35,35c28.28,23.54,93.18,72.92,194.22,123.55v23.11c0,62.32,40.21,112.85,89.8,112.85h0c49.59,0,89.8-50.53,89.8-112.85V1322.51c0-62.33-40.21-112.86-89.8-112.86h0c-47.51,0-86.4,46.38-89.58,105.07l-.22.11V1197.31c0-429.92,348.52-778.43,778.44-778.43h0c429.92,0,778.44,348.51,778.44,778.43v117.52l-.22-.11c-3.18-58.69-42.06-105.07-89.58-105.07h0c-49.59,0-89.79,50.53-89.79,112.86v570.18c0,62.32,40.2,112.85,89.79,112.85h0c49.6,0,89.8-50.53,89.8-112.85v-23.11c101.05-50.63,165.95-100,194.23-123.55a45.48,45.48,0,0,0,16.35-35V1473.34A45.48,45.48,0,0,0,2519.5,1438.39Z"
|
||||
transform="translate(-557.82 -281.22)"
|
||||
/>
|
||||
<path d="M1227.4,2429.63a108.47,108.47,0,0,0,108.47-108.47V1106.56A108.47,108.47,0,0,0,1227.4,998.08H1115.33a108.48,108.48,0,0,0-108.48,108.48v1214.6a108.47,108.47,0,0,0,108.48,108.47ZM1047.75,1289.38H1295v25.83H1047.75Z" transform="translate(-557.82 -281.22)" />
|
||||
<path d="M1602.87,2429.63a108.47,108.47,0,0,0,108.47-108.47V1106.56a108.47,108.47,0,0,0-108.47-108.48H1490.8a108.48,108.48,0,0,0-108.48,108.48v1214.6a108.47,108.47,0,0,0,108.48,108.47ZM1423.22,1289.38h247.22v25.83H1423.22Z" transform="translate(-557.82 -281.22)" />
|
||||
<path d="M1978.34,2429.63a108.47,108.47,0,0,0,108.47-108.47V1106.56a108.47,108.47,0,0,0-108.47-108.48H1866.27a108.48,108.48,0,0,0-108.48,108.48v1214.6a108.47,108.47,0,0,0,108.48,108.47ZM1798.69,1289.38h247.22v25.83H1798.69Z" transform="translate(-557.82 -281.22)" />
|
||||
<rect x="180.05" y="2185.95" width="1617.93" height="163.49" rx="81.74" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<svg fill="currentColor" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M9 3V18H12V3H9M12 5L16 18L19 17L15 4L12 5M5 5V18H8V5H5M3 19V21H21V19H3Z" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M6,19L9,15.14L11.14,17.72L14.14,13.86L18,19H6M6,4H11V12L8.5,10.5L6,12M18,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V4A2,2 0 0,0 18,2Z" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<svg class="p-px" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<svg class="p-px" viewBox="0 0 122.877 120.596">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M68.925,69.906v50.689H53.953V69.906c-4.918-2.662-8.259-7.867-8.259-13.854 c0-8.694,7.05-15.744,15.745-15.744c8.694,0,15.745,7.05,15.745,15.744C77.184,62.039,73.843,67.244,68.925,69.906L68.925,69.906z M39.32,11.165c2.916-1.438,4.111-4.969,2.673-7.882c-1.438-2.914-4.966-4.111-7.88-2.674C22.213,6.479,12.958,16.19,7.11,27.625 c-4.32,8.445-6.783,17.842-7.08,27.325c-0.299,9.563,1.587,19.223,5.973,28.114c5.401,10.953,14.558,20.695,28.039,27.592 c2.889,1.477,6.429,0.33,7.905-2.559c1.477-2.889,0.331-6.428-2.558-7.904c-11.037-5.645-18.486-13.525-22.833-22.334 c-3.506-7.111-5.014-14.857-4.774-22.539c0.243-7.757,2.256-15.442,5.79-22.348C22.304,23.721,29.76,15.879,39.32,11.165 L39.32,11.165z M88.765,0.608c-2.914-1.438-6.443-0.24-7.881,2.674c-1.438,2.914-0.242,6.445,2.674,7.882 c9.561,4.715,17.017,12.556,21.747,21.808c3.533,6.905,5.547,14.59,5.789,22.348c0.24,7.682-1.268,15.428-4.773,22.539 c-4.347,8.809-11.796,16.689-22.833,22.334c-2.889,1.477-4.034,5.016-2.558,7.904c1.476,2.889,5.016,4.035,7.905,2.559 c13.48-6.896,22.638-16.639,28.039-27.592c4.386-8.891,6.272-18.551,5.973-28.114c-0.297-9.483-2.76-18.88-7.079-27.325 C109.919,16.19,100.665,6.479,88.765,0.608L88.765,0.608z M82.791,26.505c-2.195-1.581-5.256-1.082-6.837,1.113 c-1.58,2.195-1.082,5.256,1.113,6.837c0.885,0.637,1.753,1.352,2.604,2.134c4.971,4.583,7.919,10.694,8.538,17.16 c0.626,6.524-1.111,13.437-5.518,19.552c-0.748,1.039-1.61,2.092-2.585,3.15c-1.835,1.992-1.708,5.098,0.287,6.932 c1.994,1.834,5.099,1.705,6.933-0.287c1.18-1.279,2.286-2.641,3.315-4.072c5.862-8.139,8.166-17.4,7.322-26.197 c-0.848-8.853-4.871-17.208-11.648-23.457C85.249,28.387,84.074,27.431,82.791,26.505L82.791,26.505z M45.81,34.458 c2.195-1.581,2.694-4.642,1.113-6.837c-1.581-2.195-4.642-2.694-6.837-1.114c-1.284,0.926-2.458,1.882-3.524,2.864 c-6.778,6.25-10.801,14.604-11.649,23.457c-0.844,8.796,1.46,18.06,7.323,26.199c1.031,1.43,2.136,2.791,3.315,4.07 c1.834,1.992,4.939,2.121,6.932,0.287c1.996-1.834,2.123-4.939,0.288-6.932c-0.975-1.059-1.837-2.111-2.585-3.15 c-4.406-6.115-6.144-13.027-5.518-19.551c0.619-6.465,3.567-12.577,8.538-17.16C44.058,35.81,44.926,35.095,45.81,34.458 L45.81,34.458z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
@@ -56,7 +56,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
sublist: null,
|
||||
items: [
|
||||
bookItems: [
|
||||
{
|
||||
text: 'All',
|
||||
value: 'all'
|
||||
@@ -96,6 +96,22 @@ export default {
|
||||
value: 'issues',
|
||||
sublist: false
|
||||
}
|
||||
],
|
||||
podcastItems: [
|
||||
{
|
||||
text: 'All',
|
||||
value: 'all'
|
||||
},
|
||||
{
|
||||
text: 'Genre',
|
||||
value: 'genres',
|
||||
sublist: true
|
||||
},
|
||||
{
|
||||
text: 'Tag',
|
||||
value: 'tags',
|
||||
sublist: true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -124,6 +140,13 @@ export default {
|
||||
this.$emit('update:filterBy', val)
|
||||
}
|
||||
},
|
||||
isPodcast() {
|
||||
return this.$store.getters['libraries/getCurrentLibraryMediaType'] === 'podcast'
|
||||
},
|
||||
items() {
|
||||
if (this.isPodcast) return this.podcastItems
|
||||
return this.bookItems
|
||||
},
|
||||
selectedItemSublist() {
|
||||
return this.selected && this.selected.includes('.') ? this.selected.split('.')[0] : false
|
||||
},
|
||||
|
||||
@@ -13,10 +13,7 @@
|
||||
<li :key="library.id" class="text-gray-50 select-none relative py-3 cursor-pointer hover:bg-black-400" :class="currentLibraryId === library.id ? 'bg-bg bg-opacity-80' : ''" role="option" @click="clickedOption(library)">
|
||||
<div v-show="currentLibraryId === library.id" class="absolute top-0 left-0 w-0.5 bg-warning h-full" />
|
||||
<div class="flex items-center px-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
|
||||
</svg>
|
||||
|
||||
<widgets-library-icon :icon="library.icon" />
|
||||
<span class="font-normal block truncate text-lg ml-4">{{ library.name }}</span>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -26,7 +26,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
items: [
|
||||
bookItems: [
|
||||
{
|
||||
text: 'Title',
|
||||
value: 'media.metadata.title'
|
||||
@@ -47,6 +47,32 @@ export default {
|
||||
text: 'Size',
|
||||
value: 'size'
|
||||
}
|
||||
],
|
||||
podcastItems: [
|
||||
{
|
||||
text: 'Title',
|
||||
value: 'media.metadata.title'
|
||||
},
|
||||
{
|
||||
text: 'Author',
|
||||
value: 'media.metadata.author'
|
||||
},
|
||||
{
|
||||
text: 'Added At',
|
||||
value: 'addedAt'
|
||||
},
|
||||
{
|
||||
text: 'Size',
|
||||
value: 'size'
|
||||
},
|
||||
{
|
||||
text: 'File Birthtime',
|
||||
value: 'birthtimeMs'
|
||||
},
|
||||
{
|
||||
text: 'File Modified',
|
||||
value: 'mtimeMs'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -74,6 +100,13 @@ export default {
|
||||
set(val) {
|
||||
this.$emit('update:descending', val)
|
||||
}
|
||||
},
|
||||
isPodcast() {
|
||||
return this.$store.getters['libraries/getCurrentLibraryMediaType'] === 'podcast'
|
||||
},
|
||||
items() {
|
||||
if (this.isPodcast) return this.podcastItems
|
||||
return this.bookItems
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<template>
|
||||
<div class="w-full px-2 py-3 overflow-hidden relative border-b border-white border-opacity-10">
|
||||
<div v-if="episode" class="flex items-center h-24">
|
||||
<div class="w-full px-0 py-4 overflow-hidden relative border-b border-white border-opacity-10">
|
||||
<div v-if="episode" class="flex items-center">
|
||||
<!-- <div class="w-12 min-w-12 max-w-16 h-full">
|
||||
<div class="flex h-full items-center justify-center">
|
||||
<span class="material-icons drag-handle text-lg text-white text-opacity-50 hover:text-opacity-100">menu</span>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="flex-grow px-2">
|
||||
<div class="flex-grow px-1">
|
||||
<p v-if="publishedAt" class="text-xs text-gray-400 mb-1">Published {{ $formatDate(publishedAt, 'MMM do, yyyy') }}</p>
|
||||
|
||||
<p class="text-sm font-semibold">
|
||||
{{ title }}
|
||||
</p>
|
||||
@@ -19,10 +21,8 @@
|
||||
<p class="pl-2 pr-1 text-sm font-semibold">{{ timeRemaining }}</p>
|
||||
</div>
|
||||
|
||||
<span class="material-icons px-2" :class="downloadItem ? 'animate-bounce text-warning text-opacity-75' : ''" @click="downloadClick">{{ downloadItem ? 'downloading' : 'download' }}</span>
|
||||
|
||||
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" borderless class="mx-1 mt-0.5" @click="toggleFinished" />
|
||||
<p v-if="publishedAt" class="px-4 text-sm text-gray-300">Published {{ $formatDate(publishedAt, 'MMM do, yyyy') }}</p>
|
||||
<span class="material-icons px-2" :class="downloadItem ? 'animate-bounce text-warning text-opacity-75 text-xl' : 'text-gray-300 text-xl'" @click="downloadClick">{{ downloadItem ? 'downloading' : 'download' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<p class="text-lg mb-1 font-semibold">Episodes ({{ episodes.length }})</p>
|
||||
|
||||
<template v-for="episode in episodes">
|
||||
<tables-podcast-episode-row :episode="episode" :library-item-id="libraryItemId" :key="episode.id" />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div :class="`h-${size} w-${size}`">
|
||||
<component :is="iconComponentName" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
icon: String,
|
||||
size: {
|
||||
type: Number,
|
||||
default: 5
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
iconComponentName() {
|
||||
if (this.icon === 'default') return `icons-database-svg`
|
||||
return `icons-${this.icon}-svg`
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
@@ -48,6 +48,11 @@ export default {
|
||||
bookCoverAspectRatio: this.bookCoverAspectRatio
|
||||
}
|
||||
if (this.entityName === 'series-books') props.showSequence = true
|
||||
if (this.entityName === 'books') {
|
||||
props.filterBy = this.filterBy
|
||||
props.orderBy = this.orderBy
|
||||
props.sortingIgnorePrefix = !!this.sortingIgnorePrefix
|
||||
}
|
||||
|
||||
var _this = this
|
||||
var instance = new ComponentClass({
|
||||
|
||||
@@ -24,6 +24,11 @@ export default {
|
||||
{ hid: 'description', name: 'description', content: '' },
|
||||
{ name: 'format-detection', content: 'telephone=no' }
|
||||
],
|
||||
script: [
|
||||
{
|
||||
src: '/libs/sortable.js'
|
||||
}
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
|
||||
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Ubuntu+Mono&family=Source+Sans+Pro:wght@300;400;600' },
|
||||
|
||||
Generated
+13
@@ -30494,6 +30494,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sortablejs": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.10.2.tgz",
|
||||
"integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A=="
|
||||
},
|
||||
"source-list-map": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
||||
@@ -31910,6 +31915,14 @@
|
||||
"integrity": "sha512-khZR8t3NWZ/JJ2MZxXLbesHrRJ8AKa75PY5Zq8yMifF9x8lHq8ljYkC0d2PD9yahooygQB5tcFyRDkbbIPx8hw==",
|
||||
"requires": {}
|
||||
},
|
||||
"vuedraggable": {
|
||||
"version": "2.24.3",
|
||||
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.24.3.tgz",
|
||||
"integrity": "sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==",
|
||||
"requires": {
|
||||
"sortablejs": "1.10.2"
|
||||
}
|
||||
},
|
||||
"vuex": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz",
|
||||
|
||||
+2
-1
@@ -30,7 +30,8 @@
|
||||
"libarchive.js": "^1.3.0",
|
||||
"nuxt": "^2.15.7",
|
||||
"socket.io-client": "^4.1.3",
|
||||
"vue-toastification": "^1.7.11"
|
||||
"vue-toastification": "^1.7.11",
|
||||
"vuedraggable": "^2.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.13.15",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
async asyncData({ params, app, store }) {
|
||||
async asyncData({ params, app, store, redirect }) {
|
||||
var series = await app.$axios.$get(`/api/series/${params.id}`).catch((error) => {
|
||||
console.error('Failed', error)
|
||||
return false
|
||||
|
||||
+1
-4
@@ -6,9 +6,6 @@
|
||||
<covers-book-cover :library-item="libraryItem" :width="128" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
<div v-if="!isPodcast" class="absolute bottom-0 left-0 h-1.5 bg-yellow-400 shadow-sm z-10" :style="{ width: 128 * progressPercent + 'px' }"></div>
|
||||
</div>
|
||||
<div class="flex my-4">
|
||||
<p v-if="numTracks" class="text-sm">{{ numTracks }} Tracks</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow px-3">
|
||||
<h1 class="text-lg">{{ title }}</h1>
|
||||
@@ -58,7 +55,7 @@
|
||||
</div>
|
||||
|
||||
<div class="w-full py-4">
|
||||
<p>{{ description }}</p>
|
||||
<p class="text-sm">{{ description }}</p>
|
||||
</div>
|
||||
|
||||
<tables-podcast-episodes-table v-if="isPodcast" :library-item-id="libraryItemId" :episodes="episodes" />
|
||||
|
||||
@@ -18,10 +18,36 @@
|
||||
<div v-if="isScanning" class="w-full text-center p-4">
|
||||
<p>Scanning...</p>
|
||||
</div>
|
||||
<div v-else class="w-full media-item-container overflow-y-auto">
|
||||
<div v-else class="w-full max-w-full media-item-container overflow-y-auto overflow-x-hidden relative">
|
||||
<div v-if="!isPodcast" class="w-full">
|
||||
<p class="text-base mb-2">Audio Tracks ({{ audioTracks.length }})</p>
|
||||
<template v-for="track in audioTracks">
|
||||
|
||||
<draggable v-model="audioTracksCopy" v-bind="dragOptions" handle=".drag-handle" draggable=".item" tag="div" @start="drag = true" @end="drag = false" @update="draggableUpdate">
|
||||
<transition-group type="transition" :name="!drag ? 'dragtrack' : null">
|
||||
<template v-for="track in audioTracksCopy">
|
||||
<div :key="track.localFileId" class="flex items-center my-1 item">
|
||||
<div class="w-8 h-12 flex items-center justify-center" style="min-width: 32px">
|
||||
<span class="material-icons drag-handle text-lg text-white text-opacity-50 hover:text-opacity-100">menu</span>
|
||||
</div>
|
||||
<div class="w-8 h-12 flex items-center justify-center" style="min-width: 32px">
|
||||
<p class="font-mono font-bold text-xl">{{ track.index }}</p>
|
||||
</div>
|
||||
<div class="flex-grow px-2">
|
||||
<p class="text-xs">{{ track.title }}</p>
|
||||
</div>
|
||||
<div class="w-20 text-center text-gray-300" style="min-width: 80px">
|
||||
<p class="text-xs">{{ track.mimeType }}</p>
|
||||
<p class="text-sm">{{ $elapsedPretty(track.duration) }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 flex items-center justify-center" style="min-width: 48px">
|
||||
<span class="material-icons" @click="showTrackDialog(track)">more_vert</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</transition-group>
|
||||
</draggable>
|
||||
|
||||
<!-- <template v-for="track in audioTracks">
|
||||
<div :key="track.localFileId" class="flex items-center my-1">
|
||||
<div class="w-10 h-12 flex items-center justify-center" style="min-width: 48px">
|
||||
<p class="font-mono font-bold text-xl">{{ track.index }}</p>
|
||||
@@ -37,7 +63,7 @@
|
||||
<span class="material-icons" @click="showTrackDialog(track)">more_vert</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template> -->
|
||||
</div>
|
||||
<div v-else class="w-full">
|
||||
<p class="text-base mb-2">Episodes ({{ audioTracks.length }})</p>
|
||||
@@ -87,11 +113,16 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
|
||||
import { Capacitor } from '@capacitor/core'
|
||||
import { Dialog } from '@capacitor/dialog'
|
||||
import { AbsFileSystem } from '@/plugins/capacitor'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable
|
||||
},
|
||||
asyncData({ params }) {
|
||||
return {
|
||||
localLibraryItemId: params.id
|
||||
@@ -99,8 +130,16 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
drag: false,
|
||||
dragOptions: {
|
||||
animation: 200,
|
||||
group: 'description',
|
||||
delay: 40,
|
||||
delayOnTouchOnly: true
|
||||
},
|
||||
failed: false,
|
||||
localLibraryItem: null,
|
||||
audioTracksCopy: [],
|
||||
removingItem: false,
|
||||
folderId: null,
|
||||
folder: null,
|
||||
@@ -187,6 +226,15 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
draggableUpdate() {
|
||||
console.log('Draggable update', this.audioTracksCopy)
|
||||
// var copyOfCopy = this.audioTracksCopy.map((at) => ({ ...at }))
|
||||
// const payload = {
|
||||
// localLibraryItemId: this.localLibraryItemId,
|
||||
// tracks: copyOfCopy
|
||||
// }
|
||||
// this.$db.updateLocalTrackOrder(payload)
|
||||
},
|
||||
showItemDialog() {
|
||||
this.selectedAudioTrack = null
|
||||
this.showDialog = true
|
||||
@@ -322,6 +370,8 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
this.audioTracksCopy = this.audioTracks.map((at) => ({ ...at }))
|
||||
|
||||
this.folderId = this.localLibraryItem.folderId
|
||||
this.folder = await this.$db.getLocalFolder(this.folderId)
|
||||
}
|
||||
@@ -332,9 +382,21 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style>
|
||||
.media-item-container {
|
||||
height: calc(100vh - 200px);
|
||||
max-height: calc(100vh - 200px);
|
||||
}
|
||||
.sortable-ghost {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.dragtrack-enter-from,
|
||||
.dragtrack-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
|
||||
.dragtrack-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
+24
-12
@@ -11,28 +11,37 @@
|
||||
<p class="text-lg text-gray-400">Nothing found</p>
|
||||
</div>
|
||||
<p v-if="bookResults.length" class="font-semibold text-sm mb-1">Books</p>
|
||||
<template v-for="bookResult in bookResults">
|
||||
<div :key="bookResult.audiobook.id" class="w-full h-16 py-1">
|
||||
<nuxt-link :to="`/item/${bookResult.audiobook.id}`">
|
||||
<cards-book-search-card :audiobook="bookResult.audiobook" :search="lastSearch" :match-key="bookResult.matchKey" :match-text="bookResult.matchText" />
|
||||
<template v-for="item in bookResults">
|
||||
<div :key="item.id" class="w-full h-16 py-1">
|
||||
<nuxt-link :to="`/item/${item.id}`">
|
||||
<cards-item-search-card :library-item="item.libraryItem" :match-key="item.matchKey" :match-text="item.matchText" :search="lastSearch" />
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p v-if="podcastResults.length" class="uppercase text-xs text-gray-400 my-1 px-1 font-semibold">Podcasts</p>
|
||||
<template v-for="item in podcastResults">
|
||||
<div :key="item.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1">
|
||||
<nuxt-link :to="`/item/${item.id}`">
|
||||
<cards-item-search-card :library-item="item.libraryItem" :match-key="item.matchKey" :match-text="item.matchText" :search="lastSearch" />
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p v-if="seriesResults.length" class="font-semibold text-sm mb-1 mt-2">Series</p>
|
||||
<template v-for="seriesResult in seriesResults">
|
||||
<div :key="seriesResult.series" class="w-full h-16 py-1">
|
||||
<nuxt-link :to="`/bookshelf/series/${$encode(seriesResult.series)}`">
|
||||
<cards-series-search-card :series="seriesResult.series" :book-items="seriesResult.audiobooks" />
|
||||
<div :key="seriesResult.series.id" class="w-full h-16 py-1">
|
||||
<nuxt-link :to="`/bookshelf/series/${seriesResult.series.id}`">
|
||||
<cards-series-search-card :series="seriesResult.series" :book-items="seriesResult.books" />
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p v-if="authorResults.length" class="font-semibold text-sm mb-1 mt-2">Authors</p>
|
||||
<template v-for="authorResult in authorResults">
|
||||
<div :key="authorResult.author" class="w-full h-14 py-1">
|
||||
<nuxt-link :to="`/bookshelf/library?filter=authors.${$encode(authorResult.author)}`">
|
||||
<cards-author-search-card :key="authorResult.author" :author="authorResult.author" />
|
||||
<div :key="authorResult.id" class="w-full h-14 py-1">
|
||||
<nuxt-link :to="`/bookshelf/library?filter=authors.${$encode(authorResult.id)}`">
|
||||
<cards-author-search-card :key="authorResult.id" :author="authorResult" />
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
@@ -49,6 +58,7 @@ export default {
|
||||
lastSearch: null,
|
||||
isFetching: false,
|
||||
bookResults: [],
|
||||
podcastResults: [],
|
||||
seriesResults: [],
|
||||
authorResults: []
|
||||
}
|
||||
@@ -61,7 +71,7 @@ export default {
|
||||
return this.$store.getters['getBookCoverAspectRatio']
|
||||
},
|
||||
totalResults() {
|
||||
return this.bookResults.length + this.seriesResults.length + this.authorResults.length
|
||||
return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.podcastResults.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -69,6 +79,7 @@ export default {
|
||||
this.lastSearch = value
|
||||
if (!this.lastSearch) {
|
||||
this.bookResults = []
|
||||
this.podcastResults = []
|
||||
this.seriesResults = []
|
||||
this.authorResults = []
|
||||
return
|
||||
@@ -82,7 +93,8 @@ export default {
|
||||
|
||||
this.isFetching = false
|
||||
|
||||
this.bookResults = results ? results.audiobooks || [] : []
|
||||
this.bookResults = results ? results.book || [] : []
|
||||
this.podcastResults = results ? results.podcast || [] : []
|
||||
this.seriesResults = results ? results.series || [] : []
|
||||
this.authorResults = results ? results.authors || [] : []
|
||||
},
|
||||
|
||||
@@ -111,6 +111,30 @@ class AbsDatabaseWeb extends WebPlugin {
|
||||
isLocal: true,
|
||||
localFileId: 'lf1',
|
||||
audioProbeResult: {}
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
startOffset: 0,
|
||||
duration: 15000,
|
||||
title: 'Track Title 2',
|
||||
contentUrl: 'test2',
|
||||
mimeType: 'audio/mpeg',
|
||||
metadata: null,
|
||||
isLocal: true,
|
||||
localFileId: 'lf2',
|
||||
audioProbeResult: {}
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
startOffset: 0,
|
||||
duration: 20000,
|
||||
title: 'Track Title 3',
|
||||
contentUrl: 'test3',
|
||||
mimeType: 'audio/mpeg',
|
||||
metadata: null,
|
||||
isLocal: true,
|
||||
localFileId: 'lf3',
|
||||
audioProbeResult: {}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -170,6 +194,10 @@ class AbsDatabaseWeb extends WebPlugin {
|
||||
async syncLocalMediaProgressWithServer() {
|
||||
return null
|
||||
}
|
||||
|
||||
async updateLocalTrackOrder({ localLibraryItemId, tracks }) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
const AbsDatabase = registerPlugin('AbsDatabase', {
|
||||
|
||||
@@ -94,6 +94,10 @@ class DbService {
|
||||
syncLocalMediaProgressWithServer() {
|
||||
return AbsDatabase.syncLocalMediaProgressWithServer()
|
||||
}
|
||||
|
||||
updateLocalTrackOrder(payload) {
|
||||
return AbsDatabase.updateLocalTrackOrder(payload)
|
||||
}
|
||||
}
|
||||
|
||||
export default ({ app, store }, inject) => {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/*! Sortable 1.15.0 - MIT | git://github.com/SortableJS/Sortable.git */
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sortable=e()}(this,function(){"use strict";function e(e,t){var n,o=Object.keys(e);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(e),t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),o.push.apply(o,n)),o}function M(o){for(var t=1;t<arguments.length;t++){var i=null!=arguments[t]?arguments[t]:{};t%2?e(Object(i),!0).forEach(function(t){var e,n;e=o,t=i[n=t],n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t}):Object.getOwnPropertyDescriptors?Object.defineProperties(o,Object.getOwnPropertyDescriptors(i)):e(Object(i)).forEach(function(t){Object.defineProperty(o,t,Object.getOwnPropertyDescriptor(i,t))})}return o}function o(t){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function a(){return(a=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n,o=arguments[e];for(n in o)Object.prototype.hasOwnProperty.call(o,n)&&(t[n]=o[n])}return t}).apply(this,arguments)}function i(t,e){if(null==t)return{};var n,o=function(t,e){if(null==t)return{};for(var n,o={},i=Object.keys(t),r=0;r<i.length;r++)n=i[r],0<=e.indexOf(n)||(o[n]=t[n]);return o}(t,e);if(Object.getOwnPropertySymbols)for(var i=Object.getOwnPropertySymbols(t),r=0;r<i.length;r++)n=i[r],0<=e.indexOf(n)||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n]);return o}function r(t){return function(t){if(Array.isArray(t))return l(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(t){if("string"==typeof t)return l(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);return"Map"===(n="Object"===n&&t.constructor?t.constructor.name:n)||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?l(t,e):void 0}}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function l(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,o=new Array(e);n<e;n++)o[n]=t[n];return o}function t(t){if("undefined"!=typeof window&&window.navigator)return!!navigator.userAgent.match(t)}var y=t(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i),w=t(/Edge/i),s=t(/firefox/i),u=t(/safari/i)&&!t(/chrome/i)&&!t(/android/i),n=t(/iP(ad|od|hone)/i),c=t(/chrome/i)&&t(/android/i),d={capture:!1,passive:!1};function h(t,e,n){t.addEventListener(e,n,!y&&d)}function f(t,e,n){t.removeEventListener(e,n,!y&&d)}function p(t,e){if(e&&(">"===e[0]&&(e=e.substring(1)),t))try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return}}function N(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"!==e[0]||t.parentNode===n)&&p(t,e)||o&&t===n)return t}while(t!==n&&(t=(i=t).host&&i!==document&&i.host.nodeType?i.host:i.parentNode))}var i;return null}var g,m=/\s+/g;function I(t,e,n){var o;t&&e&&(t.classList?t.classList[n?"add":"remove"](e):(o=(" "+t.className+" ").replace(m," ").replace(" "+e+" "," "),t.className=(o+(n?" "+e:"")).replace(m," ")))}function P(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];o[e=!(e in o||-1!==e.indexOf("webkit"))?"-webkit-"+e:e]=n+("string"==typeof n?"":"px")}}function v(t,e){var n="";if("string"==typeof t)n=t;else do{var o=P(t,"transform")}while(o&&"none"!==o&&(n=o+" "+n),!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(n)}function b(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i<r;i++)n(o[i],i);return o}return[]}function O(){var t=document.scrollingElement;return t||document.documentElement}function k(t,e,n,o,i){if(t.getBoundingClientRect||t===window){var r,a,l,s,c,u,d=t!==window&&t.parentNode&&t!==O()?(a=(r=t.getBoundingClientRect()).top,l=r.left,s=r.bottom,c=r.right,u=r.height,r.width):(l=a=0,s=window.innerHeight,c=window.innerWidth,u=window.innerHeight,window.innerWidth);if((e||n)&&t!==window&&(i=i||t.parentNode,!y))do{if(i&&i.getBoundingClientRect&&("none"!==P(i,"transform")||n&&"static"!==P(i,"position"))){var h=i.getBoundingClientRect();a-=h.top+parseInt(P(i,"border-top-width")),l-=h.left+parseInt(P(i,"border-left-width")),s=a+r.height,c=l+r.width;break}}while(i=i.parentNode);return o&&t!==window&&(o=(e=v(i||t))&&e.a,t=e&&e.d,e&&(s=(a/=t)+(u/=t),c=(l/=o)+(d/=o))),{top:a,left:l,bottom:s,right:c,width:d,height:u}}}function R(t,e,n){for(var o=A(t,!0),i=k(t)[e];oLine truncated
|
||||
@@ -0,0 +1,3 @@
|
||||
/*! Sortable 1.8.4 - MIT | git://github.com/SortableJS/Sortable.git */
|
||||
|
||||
!function (t) { "use strict"; "function" == typeof define && define.amd ? define(t) : "undefined" != typeof module && void 0 !== module.exports ? module.exports = t() : window.Sortable = t() }(function () { "use strict"; if ("undefined" == typeof window || !window.document) return function () { throw new Error("Sortable.js requires a window with a document") }; var U, V, f, u, q, G, h, X, Y, A, K, n, Z, Q, l, s, c, p, k, J, $, tt, et, ot, g, nt, I = [], B = !1, v = !1, it = !1, d = [], rt = !1, at = !1, m = [], i = /\s+/g, lt = "Sortable" + (new Date).getTime(), b = window, st = b.document, w = b.parseInt, ct = b.setTimeout, e = b.jQuery || b.Zepto, o = b.Polymer, r = { capture: !1, passive: !1 }, dt = !!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie|iemobile)/i), _ = !!navigator.userAgent.match(/Edge/i), y = !!navigator.userAgent.match(/firefox/i), D = !(!navigator.userAgent.match(/safari/i) || navigator.userAgent.match(/chrome/i) || navigator.userAgent.match(/android/i)), S = !!navigator.userAgent.match(/iP(ad|od|hone)/i), T = _ || dt ? "cssFloat" : "float", a = "draggable" in st.createElement("div"), C = function () { if (dt) return !1; var t = st.createElement("x"); return t.style.cssText = "pointer-events:auto", "auto" === t.style.pointerEvents }(), ht = !1, E = !1, ut = Math.abs, x = Math.min, N = Math.max, M = [], P = function (t, e) { var o = Dt(t), n = w(o.width) - w(o.paddingLeft) - w(o.paddingRight) - w(o.borderLeftWidth) - w(o.borderRightWidth), i = Mt(t, 0, e), r = Mt(t, 1, e), a = i && Dt(i), l = r && Dt(r), s = a && w(a.marginLeft) + w(a.marginRight) + Lt(i).width, c = l && w(l.marginLeft) + w(l.marginRight) + Lt(r).width; if ("flex" === o.display) return "column" === o.flexDirection || "column-reverse" === o.flexDirection ? "vertical" : "horizontal"; if ("grid" === o.display) return o.gridTemplateColumns.split(" ").length <= 1 ? "vertical" : "horizontal"; if (i && "none" !== a.float) { var d = "left" === a.float ? "left" : "right"; return !r || "both" !== l.clear && l.clear !== d ? "horizontal" : "vertical" } return i && ("block" === a.display || "flex" === a.display || "table" === a.display || "grid" === a.display || n <= s && "none" === o[T] || r && "none" === o[T] && n < s + c) ? "vertical" : "horizontal" }, O = function (t, e) { if (!t || !t.getBoundingClientRect) return H(); var o = t, n = !1; do { if (o.clientWidth < o.scrollWidth || o.clientHeight < o.scrollHeight) { var i = Dt(o); if (o.clientWidth < o.scrollWidth && ("auto" == i.overflowX || "scroll" == i.overflowX) || o.clientHeight < o.scrollHeight && ("auto" == i.overflowY || "scroll" == i.overflowY)) { if (!o || !o.getBoundingClientRect || o === st.body) return H(); if (n || e) return o; n = !0 } } } while (o = o.parentNode); return H() }, H = function () { return dt ? st.documentElement : st.scrollingElement }, ft = function (t, e, o) { t.scrollLeft += e, t.scrollTop += o }, R = It(function (o, t, e, n) { if (t.scroll) { var i = e ? e[lt] : window, r = t.scrollSensitivity, a = t.scrollSpeed, l = o.clientX, s = o.clientY, c = H(), d = !1; Y !== e && (L(), X = t.scroll, A = t.scrollFn, !0 === X && (X = O(e, !0), Y = X)); var h = 0, u = X; do { var f, p, g, v, m, b, w, _, y, D = u, S = Lt(D), T = S.top, C = S.bottom, E = S.left, x = S.right, N = S.width, M = S.height; if (f = D.scrollWidth, p = D.scrollHeight, g = Dt(D), _ = D.scrollLeft, y = D.scrollTop, w = D === c ? (b = N < f && ("auto" === g.overflowX || "scroll" === g.overflowX || "visible" === g.overflowX), M < p && ("auto" === g.overflowY || "scroll" === g.overflowY || "visible" === g.overflowY)) : (b = N < f && ("auto" === g.overflowX || "scroll" === g.overflowX), M < p && ("auto" === g.overflowY || "scroll" === g.overflowY)), v = b && (ut(x - l) <= r && _ + N < f) - (ut(E - l) <= r && !!_), m = w && (ut(C - s) <= r && y + M < p) - (ut(T - s) <= r && !!y), !I[h]) for (var P = 0; P <= h; P++)I[P] || (I[P] = {}); I[h].vx == v && I[h].vy == m && I[h].el === D || (I[h].el = D, I[h].vx = v, I[h].vy = m, clearInterval(I[h].pid), !D || 0 == v && 0 == m || (d = !0, I[h].pid = setInterval(function () { n && 0 === this.layer && (mt.active._emulateDragOver(!0), mt.active._onTouchMove(k, !0)); var t = I[this.layer].vy ? I[this.layer].vy * a : 0, e = I[this.layer].vx ? I[this.layer].vx * a : 0; "function" == typeof A && "continue" !== A.call(i, e, t, o, k, I[this.layer].el) || ft(I[this.layer].el, e, t) }.bind({ layer: h }), 24))), h++ } while (t.bubbleScroll && u !== c && (u = O(u, !1))); B = d } }, 30), L = function () { I.forEach(function (t) { clearInterval(t.pid) }), I = [] }, W = function (t) { function s(a, l) { return function (t, e, o, n) { var i = t.options.group.name && e.options.group.name && t.options.group.name === e.options.group.name; if (null == a && (l || i)) return !0; if (null == a || !1 === a) return !1; if (l && "clone" === a) return a; if ("function" == typeof a) return s(a(t, e, o, n), l)(t, e, o, n); var r = (l ? t : e).options.group.name; return !0
Line truncated
|
||||
Reference in new issue
Block a user