diff --git a/client/components/app/Appbar.vue b/client/components/app/Appbar.vue index 5bf9b794..fe1af849 100644 --- a/client/components/app/Appbar.vue +++ b/client/components/app/Appbar.vue @@ -25,15 +25,21 @@ - upload + + upload + - settings + + settings + @@ -45,10 +51,10 @@ -
-

{{ $getString('MessageItemsSelected', [numLibraryItemsSelected]) }}

+
+

{{ $getString('MessageItemsSelected', [numMediaItemsSelected]) }}

- + play_arrow {{ $strings.ButtonPlay }} @@ -109,11 +115,14 @@ export default { username() { return this.user ? this.user.username : 'err' }, - numLibraryItemsSelected() { - return this.selectedLibraryItems.length + numMediaItemsSelected() { + return this.selectedMediaItems.length }, - selectedLibraryItems() { - return this.$store.state.selectedLibraryItems + selectedMediaItems() { + return this.$store.state.globals.selectedMediaItems + }, + selectedMediaItemsArePlayable() { + return !this.selectedMediaItems.some(i => !i.hasTracks) }, userMediaProgress() { return this.$store.state.user.user.mediaProgress || [] @@ -129,8 +138,8 @@ export default { }, selectedIsFinished() { // Find an item that is not finished, if none then all items finished - return !this.selectedLibraryItems.find((libraryItemId) => { - var itemProgress = this.userMediaProgress.find((lip) => lip.libraryItemId === libraryItemId) + return !this.selectedMediaItems.find((item) => { + const itemProgress = this.userMediaProgress.find((lip) => lip.libraryItemId === item.id) return !itemProgress || !itemProgress.isFinished }) }, @@ -154,8 +163,9 @@ export default { async playSelectedItems() { 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' + const libraryItemIds = this.selectedMediaItems.map((i) => i.id) + const libraryItems = await this.$axios.$post(`/api/items/batch/get`, { libraryItemIds }).catch((error) => { + const errorMsg = error.response.data || 'Failed to get items' console.error(errorMsg, error) this.$toast.error(errorMsg) return [] @@ -185,20 +195,20 @@ export default { queueItems }) this.$store.commit('setProcessingBatch', false) - this.$store.commit('setSelectedLibraryItems', []) + this.$store.commit('globals/resetSelectedMediaItems', []) this.$eventBus.$emit('bookshelf_clear_selection') }, cancelSelectionMode() { if (this.processingBatch) return - this.$store.commit('setSelectedLibraryItems', []) + this.$store.commit('globals/resetSelectedMediaItems', []) this.$eventBus.$emit('bookshelf_clear_selection') }, toggleBatchRead() { this.$store.commit('setProcessingBatch', true) - var newIsFinished = !this.selectedIsFinished - var updateProgressPayloads = this.selectedLibraryItems.map((lid) => { + const newIsFinished = !this.selectedIsFinished + const updateProgressPayloads = this.selectedMediaItems.map((item) => { return { - libraryItemId: lid, + libraryItemId: item.id, isFinished: newIsFinished } }) @@ -208,7 +218,7 @@ export default { .then(() => { this.$toast.success('Batch update success!') this.$store.commit('setProcessingBatch', false) - this.$store.commit('setSelectedLibraryItems', []) + this.$store.commit('globals/resetSelectedMediaItems', []) this.$eventBus.$emit('bookshelf_clear_selection') }) .catch((error) => { @@ -218,18 +228,18 @@ export default { }) }, batchDeleteClick() { - 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` + const audiobookText = this.numMediaItemsSelected > 1 ? `these ${this.numMediaItemsSelected} items` : 'this item' + const 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)) { this.$store.commit('setProcessingBatch', true) this.$axios .$post(`/api/items/batch/delete`, { - libraryItemIds: this.selectedLibraryItems + libraryItemIds: this.selectedMediaItems.map((i) => i.id) }) .then(() => { this.$toast.success('Batch delete success!') this.$store.commit('setProcessingBatch', false) - this.$store.commit('setSelectedLibraryItems', []) + this.$store.commit('globals/resetSelectedMediaItems', []) this.$eventBus.$emit('bookshelf_clear_selection') }) .catch((error) => { diff --git a/client/components/app/BookShelfCategorized.vue b/client/components/app/BookShelfCategorized.vue index f17ca365..202f4992 100644 --- a/client/components/app/BookShelfCategorized.vue +++ b/client/components/app/BookShelfCategorized.vue @@ -89,8 +89,8 @@ export default { var baseSize = this.isCoverSquareAspectRatio ? 192 : 120 return this.bookCoverWidth / baseSize }, - selectedLibraryItems() { - return this.$store.state.selectedLibraryItems || [] + selectedMediaItems() { + return this.$store.state.globals.selectedMediaItems || [] } }, methods: { @@ -100,15 +100,15 @@ export default { const indexOf = shelf.shelfStartIndex + entityShelfIndex const lastLastItemIndexSelected = this.lastItemIndexSelected - if (!this.selectedLibraryItems.includes(entity.id)) { + if (!this.selectedMediaItems.some((i) => i.id === entity.id)) { this.lastItemIndexSelected = indexOf } else { this.lastItemIndexSelected = -1 } if (shiftKey && lastLastItemIndexSelected >= 0) { - var loopStart = indexOf - var loopEnd = lastLastItemIndexSelected + let loopStart = indexOf + let loopEnd = lastLastItemIndexSelected if (indexOf > lastLastItemIndexSelected) { loopStart = lastLastItemIndexSelected loopEnd = indexOf @@ -117,12 +117,12 @@ export default { const flattenedEntitiesArray = [] this.shelves.map((s) => flattenedEntitiesArray.push(...s.entities)) - var isSelecting = false + let isSelecting = false // If any items in this range is not selected then select all otherwise unselect all for (let i = loopStart; i <= loopEnd; i++) { const thisEntity = flattenedEntitiesArray[i] if (thisEntity) { - if (!this.selectedLibraryItems.includes(thisEntity.id)) { + if (!this.selectedMediaItems.some((i) => i.id === thisEntity.id)) { isSelecting = true break } @@ -133,13 +133,23 @@ export default { for (let i = loopStart; i <= loopEnd; i++) { const thisEntity = flattenedEntitiesArray[i] if (thisEntity) { - this.$store.commit('setLibraryItemSelected', { libraryItemId: thisEntity.id, selected: isSelecting }) + const mediaItem = { + id: thisEntity.id, + mediaType: thisEntity.mediaType, + hasTracks: thisEntity.mediaType === 'podcast' || thisEntity.media.numTracks || (thisEntity.media.tracks && thisEntity.media.tracks.length) + } + this.$store.commit('globals/setMediaItemSelected', { item: mediaItem, selected: isSelecting }) } else { console.error('Invalid entity index', i) } } } else { - this.$store.commit('toggleLibraryItemSelected', entity.id) + const mediaItem = { + id: entity.id, + mediaType: entity.mediaType, + hasTracks: entity.mediaType === 'podcast' || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length) + } + this.$store.commit('globals/toggleMediaItemSelected', mediaItem) } this.$nextTick(() => { diff --git a/client/components/app/BookShelfRow.vue b/client/components/app/BookShelfRow.vue index f35d72a2..931e6b50 100644 --- a/client/components/app/BookShelfRow.vue +++ b/client/components/app/BookShelfRow.vue @@ -98,7 +98,7 @@ export default { return this.$store.state.libraries.currentLibraryId }, isSelectionMode() { - return this.$store.getters['getNumLibraryItemsSelected'] > 0 + return this.$store.getters['globals/getIsBatchSelectingMediaItems'] } }, methods: { @@ -119,14 +119,14 @@ export default { this.$store.commit('globals/setShowEditPodcastEpisodeModal', true) }, updateSelectionMode(val) { - var selectedLibraryItems = this.$store.state.selectedLibraryItems + const selectedMediaItems = this.$store.state.globals.selectedMediaItems if (this.shelf.type === 'book' || this.shelf.type === 'podcast') { this.shelf.entities.forEach((ent) => { var component = this.$refs[`shelf-book-${ent.id}`] if (!component || !component.length) return component = component[0] component.setSelectionMode(val) - component.selected = selectedLibraryItems.includes(ent.id) + component.selected = selectedMediaItems.some((i) => i.id === ent.id) }) } else if (this.shelf.type === 'episode') { this.shelf.entities.forEach((ent) => { @@ -134,7 +134,7 @@ export default { if (!component || !component.length) return component = component[0] component.setSelectionMode(val) - component.selected = selectedLibraryItems.includes(ent.id) + component.selected = selectedMediaItems.some((i) => i.id === ent.id) }) } }, diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue index 9829fec0..c0de0308 100644 --- a/client/components/app/BookShelfToolbar.vue +++ b/client/components/app/BookShelfToolbar.vue @@ -205,7 +205,7 @@ export default { return this.seriesProgress.libraryItemIds || [] }, isBatchSelecting() { - return this.$store.state.selectedLibraryItems.length + return this.$store.getters['globals/getIsBatchSelectingMediaItems'] }, isSeriesFinished() { return this.seriesProgress && !!this.seriesProgress.isFinished diff --git a/client/components/app/LazyBookshelf.vue b/client/components/app/LazyBookshelf.vue index 3482e8d1..f5eee3ce 100644 --- a/client/components/app/LazyBookshelf.vue +++ b/client/components/app/LazyBookshelf.vue @@ -163,7 +163,7 @@ export default { }, bookWidth() { var coverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize') - if (this.isCoverSquareAspectRatio) return coverSize * 1.6 + if (this.isCoverSquareAspectRatio || this.entityName === 'playlists') return coverSize * 1.6 return coverSize }, bookHeight() { @@ -201,8 +201,8 @@ export default { // Includes margin return this.entityWidth + 24 }, - selectedLibraryItems() { - return this.$store.state.selectedLibraryItems || [] + selectedMediaItems() { + return this.$store.state.globals.selectedMediaItems || [] }, sizeMultiplier() { var baseSize = this.isCoverSquareAspectRatio ? 192 : 120 @@ -230,28 +230,28 @@ export default { }, selectEntity(entity, shiftKey) { if (this.entityName === 'books' || this.entityName === 'series-books') { - var indexOf = this.entities.findIndex((ent) => ent && ent.id === entity.id) + const indexOf = this.entities.findIndex((ent) => ent && ent.id === entity.id) const lastLastItemIndexSelected = this.lastItemIndexSelected - if (!this.selectedLibraryItems.includes(entity.id)) { + if (!this.selectedMediaItems.some((i) => i.id === entity.id)) { this.lastItemIndexSelected = indexOf } else { this.lastItemIndexSelected = -1 } if (shiftKey && lastLastItemIndexSelected >= 0) { - var loopStart = indexOf - var loopEnd = lastLastItemIndexSelected + let loopStart = indexOf + let loopEnd = lastLastItemIndexSelected if (indexOf > lastLastItemIndexSelected) { loopStart = lastLastItemIndexSelected loopEnd = indexOf } - var isSelecting = false + let isSelecting = false // If any items in this range is not selected then select all otherwise unselect all for (let i = loopStart; i <= loopEnd; i++) { const thisEntity = this.entities[i] if (thisEntity && !thisEntity.collapsedSeries) { - if (!this.selectedLibraryItems.includes(thisEntity.id)) { + if (!this.selectedMediaItems.some((i) => i.id === thisEntity.id)) { isSelecting = true break } @@ -269,16 +269,28 @@ export default { const entityComponentRef = this.entityComponentRefs[i] if (thisEntity && entityComponentRef) { entityComponentRef.selected = isSelecting - this.$store.commit('setLibraryItemSelected', { libraryItemId: thisEntity.id, selected: isSelecting }) + + const mediaItem = { + id: thisEntity.id, + mediaType: thisEntity.mediaType, + hasTracks: thisEntity.mediaType === 'podcast' || thisEntity.media.numTracks || (thisEntity.media.tracks && thisEntity.media.tracks.length) + } + console.log('Setting media item selected', mediaItem, 'Num Selected=', this.selectedMediaItems.length) + this.$store.commit('globals/setMediaItemSelected', { item: mediaItem, selected: isSelecting }) } else { console.error('Invalid entity index', i) } } } else { - this.$store.commit('toggleLibraryItemSelected', entity.id) + const mediaItem = { + id: entity.id, + mediaType: entity.mediaType, + hasTracks: entity.mediaType === 'podcast' || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length) + } + this.$store.commit('globals/toggleMediaItemSelected', mediaItem) } - var newIsSelectionMode = !!this.selectedLibraryItems.length + const newIsSelectionMode = !!this.selectedMediaItems.length if (this.isSelectionMode !== newIsSelectionMode) { this.isSelectionMode = newIsSelectionMode this.updateBookSelectionMode(newIsSelectionMode) diff --git a/client/components/cards/AuthorCard.vue b/client/components/cards/AuthorCard.vue index 46db9e0e..ce4f0d3b 100644 --- a/client/components/cards/AuthorCard.vue +++ b/client/components/cards/AuthorCard.vue @@ -13,10 +13,14 @@
- search + + search +
- edit + + edit +
diff --git a/client/components/modals/BatchQuickMatchModel.vue b/client/components/modals/BatchQuickMatchModel.vue index 2d70855d..0bbace4b 100644 --- a/client/components/modals/BatchQuickMatchModel.vue +++ b/client/components/modals/BatchQuickMatchModel.vue @@ -82,7 +82,7 @@ export default { return this.$store.state.globals.showBatchQuickMatchModal }, selectedBookIds() { - return this.$store.state.selectedLibraryItems || [] + return (this.$store.state.globals.selectedMediaItems || []).map((i) => i.id) }, currentLibraryId() { return this.$store.state.libraries.currentLibraryId diff --git a/client/components/modals/collections/AddCreateModal.vue b/client/components/modals/collections/AddCreateModal.vue index 09571b26..e124f9e3 100644 --- a/client/components/modals/collections/AddCreateModal.vue +++ b/client/components/modals/collections/AddCreateModal.vue @@ -104,7 +104,7 @@ export default { return this.$store.state.globals.showBatchCollectionModal }, selectedBookIds() { - return this.$store.state.selectedLibraryItems || [] + return (this.$store.state.globals.selectedMediaItems || []).map((i) => i.id) }, currentLibraryId() { return this.$store.state.libraries.currentLibraryId diff --git a/client/components/readers/Reader.vue b/client/components/readers/Reader.vue index 290ac12a..a06f4f5b 100644 --- a/client/components/readers/Reader.vue +++ b/client/components/readers/Reader.vue @@ -92,13 +92,18 @@ export default { }, ebookUrl() { if (!this.ebookFile) return null - var itemRelPath = this.selectedLibraryItem.relPath - if (itemRelPath.startsWith('/')) itemRelPath = itemRelPath.slice(1) - var relPath = this.ebookFile.metadata.relPath - if (relPath.startsWith('/')) relPath = relPath.slice(1) + let filepath = '' + if (this.selectedLibraryItem.isFile) { + filepath = this.$encodeUriPath(this.ebookFile.metadata.filename) + } else { + const itemRelPath = this.selectedLibraryItem.relPath + if (itemRelPath.startsWith('/')) itemRelPath = itemRelPath.slice(1) + const relPath = this.ebookFile.metadata.relPath + if (relPath.startsWith('/')) relPath = relPath.slice(1) - const relRelPath = this.$encodeUriPath(`${itemRelPath}/${relPath}`) - return `/ebook/${this.libraryId}/${this.folderId}/${relRelPath}` + filepath = this.$encodeUriPath(`${itemRelPath}/${relPath}`) + } + return `/ebook/${this.libraryId}/${this.folderId}/${filepath}` }, userToken() { return this.$store.getters['user/getToken'] diff --git a/client/components/widgets/AuthorsSlider.vue b/client/components/widgets/AuthorsSlider.vue index 2a93f6a1..72e02f11 100644 --- a/client/components/widgets/AuthorsSlider.vue +++ b/client/components/widgets/AuthorsSlider.vue @@ -61,7 +61,7 @@ export default { return Math.floor(this.clientWidth / (this.cardWidth + 16)) }, isSelectionMode() { - return this.$store.getters['getNumLibraryItemsSelected'] > 0 + return this.$store.getters['globals/getIsBatchSelectingMediaItems'] } }, methods: { diff --git a/client/components/widgets/EpisodeSlider.vue b/client/components/widgets/EpisodeSlider.vue index 66fd45f0..1c6c55bb 100644 --- a/client/components/widgets/EpisodeSlider.vue +++ b/client/components/widgets/EpisodeSlider.vue @@ -77,7 +77,7 @@ export default { return Math.floor(this.clientWidth / (this.cardWidth + 16)) }, isSelectionMode() { - return this.$store.getters['getNumLibraryItemsSelected'] > 0 + return this.$store.getters['globals/getIsBatchSelectingMediaItems'] } }, methods: { @@ -101,14 +101,14 @@ export default { this.updateSelectionMode(this.isSelectionMode) }, updateSelectionMode(val) { - var selectedLibraryItems = this.$store.state.selectedLibraryItems + const selectedMediaItems = this.$store.state.globals.selectedMediaItems this.items.forEach((ent) => { - var component = this.$refs[`slider-episode-${ent.recentEpisode.id}`] + let component = this.$refs[`slider-episode-${ent.recentEpisode.id}`] if (!component || !component.length) return component = component[0] component.setSelectionMode(val) - component.selected = selectedLibraryItems.includes(ent.id) + component.selected = selectedMediaItems.some((i) => i.id === ent.id) }) }, scrolled() { diff --git a/client/components/widgets/ItemSlider.vue b/client/components/widgets/ItemSlider.vue index a181d600..7ee1f390 100644 --- a/client/components/widgets/ItemSlider.vue +++ b/client/components/widgets/ItemSlider.vue @@ -63,7 +63,7 @@ export default { return Math.floor(this.clientWidth / (this.cardWidth + 16)) }, isSelectionMode() { - return this.$store.getters['getNumLibraryItemsSelected'] > 0 + return this.$store.getters['globals/getIsBatchSelectingMediaItems'] } }, methods: { @@ -82,14 +82,14 @@ export default { this.updateSelectionMode(this.isSelectionMode) }, updateSelectionMode(val) { - var selectedLibraryItems = this.$store.state.selectedLibraryItems + const selectedMediaItems = this.$store.state.globals.selectedMediaItems this.items.forEach((item) => { - var component = this.$refs[`slider-item-${item.id}`] + let component = this.$refs[`slider-item-${item.id}`] if (!component || !component.length) return component = component[0] component.setSelectionMode(val) - component.selected = selectedLibraryItems.includes(item.id) + component.selected = selectedMediaItems.some((i) => i.id === item.id) }) }, scrolled() { diff --git a/client/components/widgets/SeriesSlider.vue b/client/components/widgets/SeriesSlider.vue index b1e16c55..2244e577 100644 --- a/client/components/widgets/SeriesSlider.vue +++ b/client/components/widgets/SeriesSlider.vue @@ -61,7 +61,7 @@ export default { return Math.floor(this.clientWidth / (this.cardWidth + 16)) }, isSelectionMode() { - return this.$store.getters['getNumLibraryItemsSelected'] > 0 + return this.$store.getters['globals/getIsBatchSelectingMediaItems'] } }, methods: { diff --git a/client/layouts/default.vue b/client/layouts/default.vue index 2dd600d7..fe2aab53 100644 --- a/client/layouts/default.vue +++ b/client/layouts/default.vue @@ -42,9 +42,8 @@ export default { if (this.$store.state.showEditModal) { this.$store.commit('setShowEditModal', false) } - if (this.$store.state.selectedLibraryItems) { - this.$store.commit('setSelectedLibraryItems', []) - } + + this.$store.commit('globals/resetSelectedMediaItems', []) this.updateBodyClass() } }, @@ -504,9 +503,9 @@ export default { } // Batch selecting - if (this.$store.getters['getNumLibraryItemsSelected'] && name === 'Escape') { + if (this.$store.getters['globals/getIsBatchSelectingMediaItems'] && name === 'Escape') { // ESCAPE key cancels batch selection - this.$store.commit('setSelectedLibraryItems', []) + this.$store.commit('globals/resetSelectedMediaItems', []) this.$eventBus.$emit('bookshelf_clear_selection') e.preventDefault() return diff --git a/client/mixins/bookshelfCardsHelpers.js b/client/mixins/bookshelfCardsHelpers.js index 69b53647..26f5a9df 100644 --- a/client/mixins/bookshelfCardsHelpers.js +++ b/client/mixins/bookshelfCardsHelpers.js @@ -32,7 +32,7 @@ export default { shelfEl.appendChild(bookComponent.$el) if (this.isSelectionMode) { bookComponent.setSelectionMode(true) - if (this.selectedLibraryItems.includes(bookComponent.libraryItemId) || this.isSelectAll) { + if (this.selectedMediaItems.some(i => i.id === bookComponent.libraryItemId) || this.isSelectAll) { bookComponent.selected = true } else { bookComponent.selected = false @@ -89,7 +89,7 @@ export default { } if (this.isSelectionMode) { instance.setSelectionMode(true) - if (instance.libraryItemId && this.selectedLibraryItems.includes(instance.libraryItemId) || this.isSelectAll) { + if (instance.libraryItemId && this.selectedMediaItems.some(i => i.id === instance.libraryItemId) || this.isSelectAll) { instance.selected = true } } diff --git a/client/nuxt.config.js b/client/nuxt.config.js index 49dab56e..3efebbb7 100644 --- a/client/nuxt.config.js +++ b/client/nuxt.config.js @@ -118,6 +118,8 @@ module.exports = { ] }, workbox: { + offline: false, + cacheAssets: false, preCaching: [], runtimeCaching: [] } diff --git a/client/package-lock.json b/client/package-lock.json index f88f9558..e19f1d8e 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf-client", - "version": "2.2.6", + "version": "2.2.8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "audiobookshelf-client", - "version": "2.2.6", + "version": "2.2.8", "license": "ISC", "dependencies": { "@nuxtjs/axios": "^5.13.6", diff --git a/client/package.json b/client/package.json index 3433c4b3..d37819bb 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-client", - "version": "2.2.6", + "version": "2.2.8", "description": "Self-hosted audiobook and podcast client", "main": "index.js", "scripts": { diff --git a/client/pages/audiobook/_id/chapters.vue b/client/pages/audiobook/_id/chapters.vue index bdd99f20..a18e7e27 100644 --- a/client/pages/audiobook/_id/chapters.vue +++ b/client/pages/audiobook/_id/chapters.vue @@ -508,6 +508,8 @@ export default { this.showFindChaptersModal = false this.chapterData = null + + this.checkChapters() }, applyChapterData() { var index = 0 @@ -524,6 +526,8 @@ export default { }) this.showFindChaptersModal = false this.chapterData = null + + this.checkChapters() }, findChapters() { if (!this.asinInput) { diff --git a/client/pages/batch/index.vue b/client/pages/batch/index.vue index 1b074f1f..976005eb 100644 --- a/client/pages/batch/index.vue +++ b/client/pages/batch/index.vue @@ -91,11 +91,13 @@