diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue index b2bd0330..9829fec0 100644 --- a/client/components/app/BookShelfToolbar.vue +++ b/client/components/app/BookShelfToolbar.vue @@ -165,6 +165,9 @@ export default { isCollectionsPage() { return this.page === 'collections' }, + isPlaylistsPage() { + return this.page === 'playlists' + }, isHomePage() { return this.$route.name === 'library-library' }, @@ -185,6 +188,7 @@ export default { if (!this.page) return this.$strings.LabelBooks if (this.isSeriesPage) return this.$strings.LabelSeries if (this.isCollectionsPage) return this.$strings.LabelCollections + if (this.isPlaylistsPage) return this.$strings.LabelPlaylists return '' }, seriesId() { diff --git a/client/components/app/LazyBookshelf.vue b/client/components/app/LazyBookshelf.vue index 082d1096..fa486ee4 100644 --- a/client/components/app/LazyBookshelf.vue +++ b/client/components/app/LazyBookshelf.vue @@ -87,11 +87,11 @@ export default { emptyMessage() { if (this.page === 'series') return this.$strings.MessageBookshelfNoSeries if (this.page === 'collections') return this.$strings.MessageBookshelfNoCollections + if (this.page === 'playlists') return this.$strings.MessageNoUserPlaylists if (this.hasFilter) { if (this.filterName === 'Issues') return this.$strings.MessageNoIssues else if (this.filterName === 'Feed-open') return this.$strings.MessageBookshelfNoRSSFeeds return this.$getString('MessageBookshelfNoResultsForFilter', [this.filterName, this.filterValue]) - // return `No Results for filter "${this.filterName}: ${this.filterValue}"` } return this.$strings.MessageNoResults }, @@ -178,7 +178,7 @@ export default { return this.shelfPadding * 2 }, entityWidth() { - if (this.entityName === 'series' || this.entityName === 'collections') { + if (this.entityName === 'series' || this.entityName === 'collections' || this.entityName === 'playlists') { if (this.bookWidth * 2 > this.bookshelfWidth - this.shelfPadding) return this.bookWidth * 1.6 return this.bookWidth * 2 } @@ -302,11 +302,11 @@ export default { this.currentSFQueryString = this.buildSearchParams() } - var entityPath = this.entityName === 'books' || this.entityName === 'series-books' ? `items` : this.entityName - var sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : '' - var fullQueryString = `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1` + const entityPath = this.entityName === 'books' || this.entityName === 'series-books' ? 'items' : this.entityName + const sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : '' + const fullQueryString = `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1` - var payload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/${entityPath}${fullQueryString}`).catch((error) => { + const payload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/${entityPath}${fullQueryString}`).catch((error) => { console.error('failed to fetch books', error) return null }) diff --git a/client/components/app/SideRail.vue b/client/components/app/SideRail.vue index f42beda6..029d5095 100644 --- a/client/components/app/SideRail.vue +++ b/client/components/app/SideRail.vue @@ -71,6 +71,14 @@
+ + playlist_play + +

{{ $strings.ButtonPlaylists }}

+ +
+ + warning @@ -143,6 +151,9 @@ export default { isAuthorsPage() { return this.$route.name === 'library-library-authors' }, + isPlaylistsPage() { + return this.paramId === 'playlists' + }, libraryBookshelfPage() { return this.$route.name === 'library-library-bookshelf-id' }, @@ -173,6 +184,9 @@ export default { }, streamLibraryItem() { return this.$store.state.streamLibraryItem + }, + showPlaylists() { + return true } }, methods: { diff --git a/client/components/cards/LazyPlaylistCard.vue b/client/components/cards/LazyPlaylistCard.vue new file mode 100644 index 00000000..a08c7d38 --- /dev/null +++ b/client/components/cards/LazyPlaylistCard.vue @@ -0,0 +1,115 @@ + + + \ No newline at end of file diff --git a/client/components/modals/playlists/AddCreateModal.vue b/client/components/modals/playlists/AddCreateModal.vue index 68d06d68..88d257d9 100644 --- a/client/components/modals/playlists/AddCreateModal.vue +++ b/client/components/modals/playlists/AddCreateModal.vue @@ -75,7 +75,7 @@ export default { return selectedPlaylistItem.libraryItem.media.metadata.title || '' }, playlists() { - return this.$store.state.user.playlists || [] + return this.$store.state.libraries.userPlaylists || [] }, bookCoverAspectRatio() { return this.$store.getters['libraries/getBookCoverAspectRatio'] @@ -113,9 +113,9 @@ export default { if (!this.playlists.length) { this.processing = true this.$axios - .$get(`/api/playlists`) + .$get(`/api/libraries/${this.currentLibraryId}/playlists`) .then((data) => { - this.$store.commit('user/setPlaylists', data.playlists || []) + this.$store.commit('libraries/setUserPlaylists', data.results || []) }) .catch((error) => { console.error('Failed to get playlists', error) diff --git a/client/layouts/default.vue b/client/layouts/default.vue index c3c2a86a..b9e334f1 100644 --- a/client/layouts/default.vue +++ b/client/layouts/default.vue @@ -54,9 +54,12 @@ export default { isCasting() { return this.$store.state.globals.isCasting }, + currentLibraryId() { + return this.$store.state.libraries.currentLibraryId + }, isShowingSideRail() { if (!this.$route.name) return false - return !this.$route.name.startsWith('config') && this.$store.state.libraries.currentLibraryId + return !this.$route.name.startsWith('config') && this.currentLibraryId }, isShowingToolbar() { return this.isShowingSideRail && this.$route.name !== 'upload' && this.$route.name !== 'account' @@ -169,7 +172,7 @@ export default { this.$store.commit('libraries/remove', library) // When removed currently selected library then set next accessible library - const currLibraryId = this.$store.state.libraries.currentLibraryId + const currLibraryId = this.currentLibraryId if (currLibraryId === library.id) { var nextLibrary = this.$store.getters['libraries/getNextAccessibleLibrary'] if (nextLibrary) { @@ -208,7 +211,7 @@ export default { libraryItemRemoved(item) { if (this.$route.name.startsWith('item')) { if (this.$route.params.id === item.id) { - this.$router.replace(`/library/${this.$store.state.libraries.currentLibraryId}`) + this.$router.replace(`/library/${this.currentLibraryId}`) } } }, @@ -293,35 +296,39 @@ export default { this.$store.commit('user/updateMediaProgress', payload) }, collectionAdded(collection) { + if (this.currentLibraryId !== collection.libraryId) return this.$store.commit('libraries/addUpdateCollection', collection) }, collectionUpdated(collection) { + if (this.currentLibraryId !== collection.libraryId) return this.$store.commit('libraries/addUpdateCollection', collection) }, collectionRemoved(collection) { + if (this.currentLibraryId !== collection.libraryId) return if (this.$route.name.startsWith('collection')) { if (this.$route.params.id === collection.id) { - this.$router.replace(`/library/${this.$store.state.libraries.currentLibraryId}/bookshelf/collections`) + this.$router.replace(`/library/${this.currentLibraryId}/bookshelf/collections`) } } this.$store.commit('libraries/removeCollection', collection) }, playlistAdded(playlist) { - if (playlist.userId !== this.user.id) return - this.$store.commit('user/addUpdatePlaylist', playlist) + if (playlist.userId !== this.user.id || this.currentLibraryId !== playlist.libraryId) return + this.$store.commit('libraries/addUpdateUserPlaylist', playlist) }, playlistUpdated(playlist) { - if (playlist.userId !== this.user.id) return - this.$store.commit('user/addUpdatePlaylist', playlist) + if (playlist.userId !== this.user.id || this.currentLibraryId !== playlist.libraryId) return + this.$store.commit('libraries/addUpdateUserPlaylist', playlist) }, playlistRemoved(playlist) { - if (playlist.userId !== this.user.id) return + if (playlist.userId !== this.user.id || this.currentLibraryId !== playlist.libraryId) return + if (this.$route.name.startsWith('playlist')) { if (this.$route.params.id === playlist.id) { - this.$router.replace(`/library/${this.$store.state.libraries.currentLibraryId}/bookshelf/playlists`) + this.$router.replace(`/library/${this.currentLibraryId}/bookshelf/playlists`) } } - this.$store.commit('user/removePlaylist', playlist) + this.$store.commit('libraries/removeUserPlaylist', playlist) }, rssFeedOpen(data) { this.$store.commit('feeds/addFeed', data) diff --git a/client/mixins/bookshelfCardsHelpers.js b/client/mixins/bookshelfCardsHelpers.js index 4eec2eea..69b53647 100644 --- a/client/mixins/bookshelfCardsHelpers.js +++ b/client/mixins/bookshelfCardsHelpers.js @@ -2,6 +2,7 @@ import Vue from 'vue' import LazyBookCard from '@/components/cards/LazyBookCard' import LazySeriesCard from '@/components/cards/LazySeriesCard' import LazyCollectionCard from '@/components/cards/LazyCollectionCard' +import LazyPlaylistCard from '@/components/cards/LazyPlaylistCard' export default { data() { @@ -15,6 +16,7 @@ export default { getComponentClass() { if (this.entityName === 'series') return Vue.extend(LazySeriesCard) if (this.entityName === 'collections') return Vue.extend(LazyCollectionCard) + if (this.entityName === 'playlists') return Vue.extend(LazyPlaylistCard) return Vue.extend(LazyBookCard) }, async mountEntityCard(index) { diff --git a/client/pages/library/_library/bookshelf/_id.vue b/client/pages/library/_library/bookshelf/_id.vue index 8cc77e83..4447b899 100644 --- a/client/pages/library/_library/bookshelf/_id.vue +++ b/client/pages/library/_library/bookshelf/_id.vue @@ -16,7 +16,6 @@ export default { // Set series sort by if (params.id === 'series') { - console.log('Series page', query) if (query.sort) { store.commit('libraries/setSeriesSortBy', query.sort) store.commit('libraries/setSeriesSortDesc', !!query.desc) diff --git a/client/store/libraries.js b/client/store/libraries.js index 8e215ad7..829b27dc 100644 --- a/client/store/libraries.js +++ b/client/store/libraries.js @@ -12,7 +12,8 @@ export const state = () => ({ seriesSortBy: 'name', seriesSortDesc: false, seriesFilterBy: 'all', - collections: [] + collections: [], + userPlaylists: [] }) export const getters = { @@ -102,6 +103,8 @@ export const actions = { return false } + const libraryChanging = state.currentLibraryId !== libraryId + return this.$axios .$get(`/api/libraries/${libraryId}?include=filterdata`) .then((data) => { @@ -115,7 +118,10 @@ export const actions = { commit('setLibraryIssues', issues) commit('setLibraryFilterData', filterData) commit('setCurrentLibrary', libraryId) - commit('setCollections', []) + if (libraryChanging) { + commit('setCollections', []) + commit('setUserPlaylists', []) + } return data }) .catch((error) => { @@ -320,5 +326,19 @@ export const mutations = { }, removeCollection(state, collection) { state.collections = state.collections.filter(c => c.id !== collection.id) + }, + setUserPlaylists(state, playlists) { + state.userPlaylists = playlists + }, + addUpdateUserPlaylist(state, playlist) { + const index = state.userPlaylists.findIndex(p => p.id === playlist.id) + if (index >= 0) { + state.userPlaylists.splice(index, 1, playlist) + } else { + state.userPlaylists.push(playlist) + } + }, + removeUserPlaylist(state, playlist) { + state.userPlaylists = state.userPlaylists.filter(p => p.id !== playlist.id) } } \ No newline at end of file diff --git a/client/store/user.js b/client/store/user.js index d6b82306..7b57b169 100644 --- a/client/store/user.js +++ b/client/store/user.js @@ -9,8 +9,7 @@ export const state = () => ({ collapseSeries: false, collapseBookSeries: false }, - settingsListeners: [], - playlists: [] + settingsListeners: [] }) export const getters = { @@ -164,19 +163,5 @@ export const mutations = { }, removeSettingsListener(state, listenerId) { state.settingsListeners = state.settingsListeners.filter(l => l.id !== listenerId) - }, - setPlaylists(state, playlists) { - state.playlists = playlists - }, - addUpdatePlaylist(state, playlist) { - const indexOf = state.playlists.findIndex(p => p.id == playlist.id) - if (indexOf >= 0) { - state.playlists.splice(indexOf, 1, playlist) - } else { - state.playlists.push(playlist) - } - }, - removePlaylist(state, playlist) { - state.playlists = state.playlists.filter(p => p.id !== playlist.id) } } \ No newline at end of file diff --git a/client/strings/en-us.json b/client/strings/en-us.json index 8d5f5f0f..995eb84b 100644 --- a/client/strings/en-us.json +++ b/client/strings/en-us.json @@ -41,6 +41,7 @@ "ButtonOpenManager": "Open Manager", "ButtonPlay": "Play", "ButtonPlaying": "Playing", + "ButtonPlaylists": "Playlists", "ButtonPurgeAllCache": "Purge All Cache", "ButtonPurgeItemsCache": "Purge Items Cache", "ButtonPurgeMediaProgress": "Purge Media Progress", diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index b724ed0c..aec012d7 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -422,6 +422,26 @@ class LibraryController { res.json(payload) } + // api/libraries/:id/playlists + async getUserPlaylistsForLibrary(req, res) { + let playlistsForUser = this.db.playlists.filter(p => p.userId === req.user.id && p.libraryId === req.library.id).map(p => p.toJSONExpanded(this.db.libraryItems)) + + const payload = { + results: [], + total: playlistsForUser.length, + limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0, + page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0 + } + + if (payload.limit) { + const startIndex = payload.page * payload.limit + playlistsForUser = playlistsForUser.slice(startIndex, startIndex + payload.limit) + } + + payload.results = playlistsForUser + res.json(payload) + } + async getLibraryFilterData(req, res) { res.json(libraryHelpers.getDistinctFilterDataNew(req.libraryItems)) } diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index f67bb768..df32468a 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -72,6 +72,7 @@ class ApiRouter { this.router.delete('/libraries/:id/issues', LibraryController.middleware.bind(this), LibraryController.removeLibraryItemsWithIssues.bind(this)) this.router.get('/libraries/:id/series', LibraryController.middleware.bind(this), LibraryController.getAllSeriesForLibrary.bind(this)) this.router.get('/libraries/:id/collections', LibraryController.middleware.bind(this), LibraryController.getCollectionsForLibrary.bind(this)) + this.router.get('/libraries/:id/playlists', LibraryController.middleware.bind(this), LibraryController.getUserPlaylistsForLibrary.bind(this)) this.router.get('/libraries/:id/personalized', LibraryController.middleware.bind(this), LibraryController.getLibraryUserPersonalizedOptimal.bind(this)) this.router.get('/libraries/:id/filterdata', LibraryController.middleware.bind(this), LibraryController.getLibraryFilterData.bind(this)) this.router.get('/libraries/:id/search', LibraryController.middleware.bind(this), LibraryController.search.bind(this))