Add:Batch select audiobook play button, item page mobile screen size cleanup

This commit is contained in:
advplyr 2022-11-19 10:20:10 -06:00
parent 8931702f1b
commit 7485cf1a26
5 changed files with 62 additions and 24 deletions

View File

@ -26,7 +26,7 @@
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
} }
.material-icons:not(.text-xs):not(.text-sm):not(.text-md):not(.text-base):not(.text-lg):not(.text-xl):not(.text-2xl):not(.text-3xl):not(.text-4xl):not(.text-5xl):not(.text-6xl):not(.text-7xl):not(.text-8xl) { .material-icons:not([class*="text-"]) {
font-size: 1.5rem; font-size: 1.5rem;
} }
@ -44,11 +44,10 @@
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
} }
.material-icons-outlined:not(.text-xs):not(.text-sm):not(.text-md):not(.text-base):not(.text-lg):not(.text-xl):not(.text-2xl):not(.text-3xl):not(.text-4xl):not(.text-5xl):not(.text-6xl):not(.text-7xl):not(.text-8xl) { .material-icons-outlined:not([class*="text-"]) {
font-size: 1.5rem; font-size: 1.5rem;
} }
@font-face { @font-face {
font-family: 'Gentium Book Basic'; font-family: 'Gentium Book Basic';
font-style: normal; font-style: normal;

View File

@ -46,8 +46,12 @@
</nuxt-link> </nuxt-link>
</div> </div>
<div v-show="numLibraryItemsSelected" class="absolute top-0 left-0 w-full h-full px-4 bg-primary flex items-center"> <div v-show="numLibraryItemsSelected" class="absolute top-0 left-0 w-full h-full px-4 bg-primary flex items-center">
<h1 class="text-2xl px-4">{{ $getString('MessageItemsSelected', [numLibraryItemsSelected]) }}</h1> <h1 class="text-lg md:text-2xl px-4">{{ $getString('MessageItemsSelected', [numLibraryItemsSelected]) }}</h1>
<div class="flex-grow" /> <div class="flex-grow" />
<ui-btn v-if="!isPodcastLibrary" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="playSelectedItems">
<span class="material-icons -ml-2 pr-1 text-white">play_arrow</span>
{{ $strings.ButtonPlay }}
</ui-btn>
<ui-tooltip v-if="userIsAdminOrUp && !isPodcastLibrary" :text="$strings.ButtonQuickMatch" direction="bottom"> <ui-tooltip v-if="userIsAdminOrUp && !isPodcastLibrary" :text="$strings.ButtonQuickMatch" direction="bottom">
<ui-icon-btn :disabled="processingBatch" icon="auto_awesome" @click="batchAutoMatchClick" class="mx-1.5" /> <ui-icon-btn :disabled="processingBatch" icon="auto_awesome" @click="batchAutoMatchClick" class="mx-1.5" />
</ui-tooltip> </ui-tooltip>
@ -59,14 +63,14 @@
</ui-tooltip> </ui-tooltip>
<template v-if="userCanUpdate && numLibraryItemsSelected < 50"> <template v-if="userCanUpdate && numLibraryItemsSelected < 50">
<ui-tooltip text="Edit" direction="bottom"> <ui-tooltip text="Edit" direction="bottom">
<ui-icon-btn v-show="!processingBatchDelete" icon="edit" bg-color="warning" class="mx-1.5" @click="batchEditClick" /> <ui-icon-btn :disabled="processingBatch" icon="edit" bg-color="warning" class="mx-1.5" @click="batchEditClick" />
</ui-tooltip> </ui-tooltip>
</template> </template>
<ui-tooltip v-if="userCanDelete" :text="$strings.ButtonRemove" direction="bottom"> <ui-tooltip v-if="userCanDelete" :text="$strings.ButtonRemove" direction="bottom">
<ui-icon-btn :disabled="processingBatchDelete" icon="delete" bg-color="error" class="mx-1.5" @click="batchDeleteClick" /> <ui-icon-btn :disabled="processingBatch" icon="delete" bg-color="error" class="mx-1.5" @click="batchDeleteClick" />
</ui-tooltip> </ui-tooltip>
<ui-tooltip :text="$strings.LabelDeselectAll" direction="bottom"> <ui-tooltip :text="$strings.LabelDeselectAll" direction="bottom">
<span class="material-icons text-4xl px-4 hover:text-gray-100 cursor-pointer" :class="processingBatchDelete ? 'text-gray-400' : ''" @click="cancelSelectionMode">close</span> <span class="material-icons text-4xl px-4 hover:text-gray-100 cursor-pointer" :class="processingBatch ? 'text-gray-400' : ''" @click="cancelSelectionMode">close</span>
</ui-tooltip> </ui-tooltip>
</div> </div>
</div> </div>
@ -77,9 +81,7 @@
export default { export default {
data() { data() {
return { return {
processingBatchDelete: false, totalEntities: 0
totalEntities: 0,
isAllSelected: false
} }
}, },
computed: { computed: {
@ -149,11 +151,47 @@ export default {
} }
}, },
methods: { methods: {
cancelSelectionMode() { async playSelectedItems() {
if (this.processingBatchDelete) return this.$store.commit('setProcessingBatch', true)
var libraryItems = await this.$axios.$post(`/api/items/batch/get`, { libraryItemIds: this.selectedLibraryItems }).catch((error) => {
var errorMsg = error.response.data || 'Failed to get items'
console.error(errorMsg, error)
this.$toast.error(errorMsg)
return []
})
if (!libraryItems.length) {
this.$store.commit('setProcessingBatch', false)
return
}
const queueItems = []
libraryItems.forEach((item) => {
queueItems.push({
libraryItemId: item.id,
libraryId: item.libraryId,
episodeId: null,
title: item.media.metadata.title,
subtitle: item.media.metadata.authors.map((au) => au.name).join(', '),
caption: '',
duration: item.media.duration || null,
coverPath: item.media.coverPath || null
})
})
this.$eventBus.$emit('play-item', {
libraryItemId: queueItems[0].libraryItemId,
queueItems
})
this.$store.commit('setProcessingBatch', false)
this.$store.commit('setSelectedLibraryItems', [])
this.$eventBus.$emit('bookshelf_clear_selection')
},
cancelSelectionMode() {
if (this.processingBatch) return
this.$store.commit('setSelectedLibraryItems', []) this.$store.commit('setSelectedLibraryItems', [])
this.$eventBus.$emit('bookshelf_clear_selection') this.$eventBus.$emit('bookshelf_clear_selection')
this.isAllSelected = false
}, },
toggleBatchRead() { toggleBatchRead() {
this.$store.commit('setProcessingBatch', true) this.$store.commit('setProcessingBatch', true)
@ -183,7 +221,6 @@ export default {
var audiobookText = this.numLibraryItemsSelected > 1 ? `these ${this.numLibraryItemsSelected} items` : 'this item' var audiobookText = this.numLibraryItemsSelected > 1 ? `these ${this.numLibraryItemsSelected} items` : 'this item'
var confirmMsg = `Are you sure you want to remove ${audiobookText}?\n\n*Does not delete your files, only removes the items from Audiobookshelf` var confirmMsg = `Are you sure you want to remove ${audiobookText}?\n\n*Does not delete your files, only removes the items from Audiobookshelf`
if (confirm(confirmMsg)) { if (confirm(confirmMsg)) {
this.processingBatchDelete = true
this.$store.commit('setProcessingBatch', true) this.$store.commit('setProcessingBatch', true)
this.$axios this.$axios
.$post(`/api/items/batch/delete`, { .$post(`/api/items/batch/delete`, {
@ -191,7 +228,6 @@ export default {
}) })
.then(() => { .then(() => {
this.$toast.success('Batch delete success!') this.$toast.success('Batch delete success!')
this.processingBatchDelete = false
this.$store.commit('setProcessingBatch', false) this.$store.commit('setProcessingBatch', false)
this.$store.commit('setSelectedLibraryItems', []) this.$store.commit('setSelectedLibraryItems', [])
this.$eventBus.$emit('bookshelf_clear_selection') this.$eventBus.$emit('bookshelf_clear_selection')
@ -199,7 +235,6 @@ export default {
.catch((error) => { .catch((error) => {
this.$toast.error('Batch delete failed') this.$toast.error('Batch delete failed')
console.error('Failed to batch delete', error) console.error('Failed to batch delete', error)
this.processingBatchDelete = false
this.$store.commit('setProcessingBatch', false) this.$store.commit('setProcessingBatch', false)
}) })
} }

View File

@ -7,7 +7,7 @@
<controls-volume-control ref="volumeControl" v-model="volume" @input="setVolume" class="mx-2 hidden md:block" /> <controls-volume-control ref="volumeControl" v-model="volume" @input="setVolume" class="mx-2 hidden md:block" />
<div class="cursor-pointer text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showSleepTimer')"> <div class="cursor-pointer text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showSleepTimer')">
<span v-if="!sleepTimerSet" class="material-icons text-2xl sm:text-2.5xl">snooze</span> <span v-if="!sleepTimerSet" class="material-icons text-2xl">snooze</span>
<div v-else class="flex items-center"> <div v-else class="flex items-center">
<span class="material-icons text-lg text-warning">snooze</span> <span class="material-icons text-lg text-warning">snooze</span>
<p class="text-xl text-warning font-mono font-semibold text-center px-0.5 pb-0.5" style="min-width: 30px">{{ sleepTimerRemainingString }}</p> <p class="text-xl text-warning font-mono font-semibold text-center px-0.5 pb-0.5" style="min-width: 30px">{{ sleepTimerRemainingString }}</p>
@ -15,15 +15,15 @@
</div> </div>
<div v-if="!isPodcast" class="cursor-pointer text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showBookmarks')"> <div v-if="!isPodcast" class="cursor-pointer text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showBookmarks')">
<span class="material-icons text-2xl sm:text-2.5xl">{{ bookmarks.length ? 'bookmarks' : 'bookmark_border' }}</span> <span class="material-icons text-2xl">{{ bookmarks.length ? 'bookmarks' : 'bookmark_border' }}</span>
</div> </div>
<div v-if="chapters.length" class="cursor-pointer text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="showChapters"> <div v-if="chapters.length" class="cursor-pointer text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="showChapters">
<span class="material-icons text-2xl sm:text-3xl">format_list_bulleted</span> <span class="material-icons text-2xl">format_list_bulleted</span>
</div> </div>
<button v-if="playerQueueItems.length" class="outline-none text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showPlayerQueueItems')"> <button v-if="playerQueueItems.length" class="outline-none text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showPlayerQueueItems')">
<span class="material-icons text-2xl sm:text-3xl">queue_music</span> <span class="material-icons text-2.5xl sm:text-3xl">queue_music</span>
</button> </button>
<ui-tooltip v-if="chapters.length" direction="top" :text="useChapterTrack ? $strings.LabelUseFullTrack : $strings.LabelUseChapterTrack"> <ui-tooltip v-if="chapters.length" direction="top" :text="useChapterTrack ? $strings.LabelUseFullTrack : $strings.LabelUseChapterTrack">

View File

@ -34,7 +34,7 @@
<template v-if="!isVideo"> <template v-if="!isVideo">
<p v-if="isPodcast" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl">by {{ podcastAuthor || 'Unknown' }}</p> <p v-if="isPodcast" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl">by {{ podcastAuthor || 'Unknown' }}</p>
<p v-else-if="authors.length" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl"> <p v-else-if="authors.length" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl max-w-[calc(100vw-2rem)] overflow-hidden overflow-ellipsis">
by <nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">,&nbsp;</span></nuxt-link> by <nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">,&nbsp;</span></nuxt-link>
</p> </p>
<p v-else class="mb-2 mt-0.5 text-gray-200 text-xl">by Unknown</p> <p v-else class="mb-2 mt-0.5 text-gray-200 text-xl">by Unknown</p>
@ -44,7 +44,7 @@
<div class="w-32"> <div class="w-32">
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelNarrators }}</span> <span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelNarrators }}</span>
</div> </div>
<div> <div class="max-w-[calc(100vw-10rem)] overflow-hidden overflow-ellipsis">
<template v-for="(narrator, index) in narrators"> <template v-for="(narrator, index) in narrators">
<nuxt-link :key="narrator" :to="`/library/${libraryId}/bookshelf?filter=narrators.${$encode(narrator)}`" class="hover:underline">{{ narrator }}</nuxt-link <nuxt-link :key="narrator" :to="`/library/${libraryId}/bookshelf?filter=narrators.${$encode(narrator)}`" class="hover:underline">{{ narrator }}</nuxt-link
><span :key="index" v-if="index < narrators.length - 1">,&nbsp;</span> ><span :key="index" v-if="index < narrators.length - 1">,&nbsp;</span>
@ -63,7 +63,7 @@
<div class="w-32"> <div class="w-32">
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelGenres }}</span> <span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelGenres }}</span>
</div> </div>
<div> <div class="max-w-[calc(100vw-10rem)] overflow-hidden overflow-ellipsis">
<template v-for="(genre, index) in genres"> <template v-for="(genre, index) in genres">
<nuxt-link :key="genre" :to="`/library/${libraryId}/bookshelf?filter=genres.${$encode(genre)}`" class="hover:underline">{{ genre }}</nuxt-link <nuxt-link :key="genre" :to="`/library/${libraryId}/bookshelf?filter=genres.${$encode(genre)}`" class="hover:underline">{{ genre }}</nuxt-link
><span :key="index" v-if="index < genres.length - 1">,&nbsp;</span> ><span :key="index" v-if="index < genres.length - 1">,&nbsp;</span>

View File

@ -301,7 +301,11 @@ class LibraryItemController {
if (!libraryItemIds.length) { if (!libraryItemIds.length) {
return res.status(403).send('Invalid payload') return res.status(403).send('Invalid payload')
} }
var libraryItems = this.db.libraryItems.filter(li => libraryItemIds.includes(li.id)).map((li) => li.toJSONExpanded()) var libraryItems = []
libraryItemIds.forEach((lid) => {
const li = this.db.libraryItems.find(_li => _li.id === lid)
if (li) libraryItems.push(li.toJSONExpanded())
})
res.json(libraryItems) res.json(libraryItems)
} }