diff --git a/client/components/app/BookShelfCategorized.vue b/client/components/app/BookShelfCategorized.vue index ceb79237..043faaca 100644 --- a/client/components/app/BookShelfCategorized.vue +++ b/client/components/app/BookShelfCategorized.vue @@ -244,6 +244,24 @@ export default { this.libraryItemUpdated(li) }) }, + seriesUpdated(series) { + if (series.hideFromHome) { + this.shelves.forEach((shelf) => { + if (shelf.type == 'book' && shelf.id == 'continue-series') { + // Filter out series books from continue series shelf + shelf.entities = shelf.entities.filter((ent) => { + if (ent.media.metadata.series && ent.media.metadata.series.id == series.id) return false + return true + }) + } else if (shelf.type == 'series') { + // Filter out series from series shelf + shelf.entities = shelf.entities.filter((ent) => { + return ent.id != series.id + }) + } + }) + } + }, authorUpdated(author) { this.shelves.forEach((shelf) => { if (shelf.type == 'authors') { @@ -270,6 +288,7 @@ export default { this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated }) if (this.$root.socket) { + this.$root.socket.on('series_updated', this.seriesUpdated) this.$root.socket.on('author_updated', this.authorUpdated) this.$root.socket.on('author_removed', this.authorRemoved) this.$root.socket.on('item_updated', this.libraryItemUpdated) @@ -285,6 +304,7 @@ export default { this.$store.commit('user/removeSettingsListener', 'bookshelf') if (this.$root.socket) { + this.$root.socket.off('series_updated', this.seriesUpdated) this.$root.socket.off('author_updated', this.authorUpdated) this.$root.socket.off('author_removed', this.authorRemoved) this.$root.socket.off('item_updated', this.libraryItemUpdated) diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue index 96dff6c9..1e41ce14 100644 --- a/client/components/app/BookShelfToolbar.vue +++ b/client/components/app/BookShelfToolbar.vue @@ -21,13 +21,14 @@ <template v-if="page !== 'search' && page !== 'podcast-search' && page !== 'recent-episodes' && !isHome"> <p v-if="!selectedSeries" class="font-book hidden md:block">{{ numShowing }} {{ entityName }}</p> <div v-else class="items-center hidden md:flex w-full"> - <p class="pl-4 font-book text-lg"> + <p class="pl-2 font-book text-lg"> {{ seriesName }} </p> <div class="w-6 h-6 rounded-full bg-black bg-opacity-30 flex items-center justify-center ml-3"> <span class="font-mono">{{ numShowing }}</span> </div> <div class="flex-grow" /> + <ui-btn v-if="seriesHideFromHome" :loading="processingSeriesHideFromHome" color="primary" small class="mr-2" @click="showSeriesOnHome">Show on Home</ui-btn> <ui-btn color="primary" small :loading="processingSeries" class="flex items-center" @click="markSeriesFinished"> <div class="h-5 w-5"> <svg v-if="isSeriesFinished" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgb(63, 181, 68)"> @@ -37,8 +38,8 @@ <path d="M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-7 19.6l-7-4.66V3h14v12.93l-7 4.67zm-2.01-7.42l-2.58-2.59L6 12l4 4 8-8-1.42-1.42z" /> </svg> </div> - <span class="pl-2"> Mark Series {{ isSeriesFinished ? 'Not Finished' : 'Finished' }}</span></ui-btn - > + <span class="pl-2"> Mark Series {{ isSeriesFinished ? 'Not Finished' : 'Finished' }}</span> + </ui-btn> </div> <div class="flex-grow hidden sm:inline-block" /> @@ -83,7 +84,7 @@ export default { authors: { type: Array, default: () => [] - }, + } }, data() { return { @@ -94,7 +95,8 @@ export default { keywordTimeout: null, processingSeries: false, processingIssues: false, - processingAuthors: false + processingAuthors: false, + processingSeriesHideFromHome: false } }, computed: { @@ -150,6 +152,9 @@ export default { seriesName() { return this.selectedSeries ? this.selectedSeries.name : null }, + seriesHideFromHome() { + return this.selectedSeries ? this.selectedSeries.hideFromHome : null + }, seriesProgress() { return this.selectedSeries ? this.selectedSeries.progress : null }, @@ -171,6 +176,23 @@ export default { } }, methods: { + async showSeriesOnHome() { + this.processingSeriesHideFromHome = true + const seriesId = this.selectedSeries.id + this.$axios + .$patch(`/api/series/${seriesId}`, { hideFromHome: false }) + .then((data) => { + console.log('Updated series', data) + this.$toast.success('Series updated successfully') + }) + .catch((error) => { + console.error('Failed to update series', error) + this.$toast.error('Failed to update series') + }) + .finally(() => { + this.processingSeriesHideFromHome = false + }) + }, async matchAllAuthors() { this.processingAuthors = true diff --git a/client/components/cards/LazyBookCard.vue b/client/components/cards/LazyBookCard.vue index be101e82..fe304a0f 100644 --- a/client/components/cards/LazyBookCard.vue +++ b/client/components/cards/LazyBookCard.vue @@ -411,6 +411,12 @@ export default { text: 'Re-Scan' }) } + if (this.userIsAdminOrUp && this.series && this.bookMount) { + items.push({ + func: 'hideSeriesFromHome', + text: 'Hide Series from Home' + }) + } return items }, _socket() { @@ -572,11 +578,12 @@ export default { } else if (result === 'REMOVED') { this.$toast.error(`Re-Scan complete item was removed`) } - this.processing = false }) .catch((error) => { console.error('Failed to scan library item', error) this.$toast.error('Failed to scan library item') + }) + .finally(() => { this.processing = false }) }, @@ -588,6 +595,22 @@ export default { // More menu func this.store.commit('showEditModalOnTab', { libraryItem: this.libraryItem, tab: 'match' }) }, + hideSeriesFromHome() { + const axios = this.$axios || this.$nuxt.$axios + this.processing = true + axios + .$patch(`/api/series/${this.series.id}`, { hideFromHome: true }) + .then((data) => { + console.log('Series updated', data) + }) + .catch((error) => { + console.error('Failed to hide series from home', error) + this.$toast.error('Failed to update series') + }) + .finally(() => { + this.processing = false + }) + }, openCollections() { this.store.commit('setSelectedLibraryItem', this.libraryItem) this.store.commit('globals/setShowUserCollectionsModal', true) diff --git a/client/pages/library/_library/series/_id.vue b/client/pages/library/_library/series/_id.vue index 9970ae5a..e07a055f 100644 --- a/client/pages/library/_library/series/_id.vue +++ b/client/pages/library/_library/series/_id.vue @@ -40,7 +40,20 @@ export default { return this.$store.state.streamLibraryItem } }, - mounted() {}, - beforeDestroy() {} + methods: { + seriesUpdated(series) { + this.series = series + } + }, + mounted() { + if (this.$root.socket) { + this.$root.socket.on('series_updated', this.seriesUpdated) + } + }, + beforeDestroy() { + if (this.$root.socket) { + this.$root.socket.off('series_updated', this.seriesUpdated) + } + } } </script> diff --git a/server/controllers/SeriesController.js b/server/controllers/SeriesController.js index fcd2e0a1..9f11ac51 100644 --- a/server/controllers/SeriesController.js +++ b/server/controllers/SeriesController.js @@ -34,6 +34,15 @@ class SeriesController { res.json(series) } + async update(req, res) { + const hasUpdated = req.series.update(req.body) + if (hasUpdated) { + await this.db.updateEntity('series', req.series) + this.emitter('series_updated', req.series) + } + res.json(req.series) + } + middleware(req, res, next) { var series = this.db.series.find(se => se.id === req.params.id) if (!series) return res.sendStatus(404) diff --git a/server/objects/entities/Series.js b/server/objects/entities/Series.js index 9cb38a0b..feafc518 100644 --- a/server/objects/entities/Series.js +++ b/server/objects/entities/Series.js @@ -5,6 +5,7 @@ class Series { this.id = null this.name = null this.description = null + this.hideFromHome = false this.addedAt = null this.updatedAt = null @@ -17,6 +18,7 @@ class Series { this.id = series.id this.name = series.name this.description = series.description || null + this.hideFromHome = !!series.hideFromHome this.addedAt = series.addedAt this.updatedAt = series.updatedAt } @@ -26,6 +28,7 @@ class Series { id: this.id, name: this.name, description: this.description, + hideFromHome: this.hideFromHome, addedAt: this.addedAt, updatedAt: this.updatedAt } @@ -43,10 +46,24 @@ class Series { this.id = getId('ser') this.name = data.name this.description = data.description || null + this.hideFromHome = !!data.hideFromHome this.addedAt = Date.now() this.updatedAt = Date.now() } + update(series) { + if (!series) return false + const keysToUpdate = ['name', 'description', 'hideFromHome'] + var hasUpdated = false + for (const key of keysToUpdate) { + if (series[key] !== undefined && series[key] !== this[key]) { + this[key] = series[key] + hasUpdated = true + } + } + return hasUpdated + } + checkNameEquals(name) { if (!name || !this.name) return false return this.name.toLowerCase() == name.toLowerCase().trim() diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index 0e082b86..449f8a2d 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -178,6 +178,7 @@ class ApiRouter { // this.router.get('/series/search', SeriesController.search.bind(this)) this.router.get('/series/:id', SeriesController.middleware.bind(this), SeriesController.findOne.bind(this)) + this.router.patch('/series/:id', SeriesController.middleware.bind(this), SeriesController.update.bind(this)) // // Playback Session Routes diff --git a/server/utils/libraryHelpers.js b/server/utils/libraryHelpers.js index 23993344..72ade464 100644 --- a/server/utils/libraryHelpers.js +++ b/server/utils/libraryHelpers.js @@ -417,7 +417,7 @@ module.exports = { if (!seriesMap[librarySeries.id]) { const seriesObj = allSeries.find(se => se.id === librarySeries.id) - if (seriesObj) { + if (seriesObj && !seriesObj.hideFromHome) { var series = { ...seriesObj.toJSON(), books: [libraryItemJson],