diff --git a/components/app/AudioPlayerContainer.vue b/components/app/AudioPlayerContainer.vue index e4d8fa66..4da0eb85 100644 --- a/components/app/AudioPlayerContainer.vue +++ b/components/app/AudioPlayerContainer.vue @@ -450,18 +450,6 @@ export default { this.$refs.audioPlayer.setPlaybackSpeed(this.playbackSpeed) } }, - streamUpdated(type, data) { - if (type === 'download') { - if (data) { - this.download = { ...data } - if (this.audioPlayerReady) { - this.playDownload() - } - } else if (this.download) { - this.cancelStream() - } - } - }, setListeners() { if (!this.$server.socket) { console.error('Invalid server socket not set') @@ -481,6 +469,16 @@ export default { this.$refs.audioPlayer.terminateStream() } } + }, + async playLibraryItem(libraryItemId) { + var libraryItem = await this.$axios.$get(`/api/items/${libraryItemId}?expanded=1`).catch((error) => { + console.error('Failed to fetch full item', error) + return null + }) + if (!libraryItem) return + this.$store.commit('setLibraryItemStream', libraryItem) + + // TODO: Call load library item in native } }, mounted() { @@ -491,9 +489,9 @@ export default { console.log(`[AudioPlayerContainer] Init Playback Speed: ${this.playbackSpeed}`) this.setListeners() + this.$eventBus.$on('play-item', this.playLibraryItem) this.$eventBus.$on('close_stream', this.closeStreamOnly) this.$store.commit('user/addSettingsListener', { id: 'streamContainer', meth: this.settingsUpdated }) - this.$store.commit('setStreamListener', this.streamUpdated) }, beforeDestroy() { if (this.onSleepTimerEndedListener) this.onSleepTimerEndedListener.remove() @@ -506,10 +504,9 @@ export default { this.$server.socket.off('stream_ready', this.streamReady) this.$server.socket.off('stream_reset', this.streamReset) } - + this.$eventBus.$off('play-item', this.playLibraryItem) this.$eventBus.$off('close_stream', this.closeStreamOnly) this.$store.commit('user/removeSettingsListener', 'streamContainer') - this.$store.commit('removeStreamListener') } } \ No newline at end of file diff --git a/components/bookshelf/LazyBookshelf.vue b/components/bookshelf/LazyBookshelf.vue index 803a22d3..0874849b 100644 --- a/components/bookshelf/LazyBookshelf.vue +++ b/components/bookshelf/LazyBookshelf.vue @@ -144,19 +144,29 @@ export default { if (!this.initialized) { this.currentSFQueryString = this.buildSearchParams() } - var entityPath = this.entityName === 'books' ? `books/all` : this.entityName + // var entityPath = this.entityName === 'books' ? `books/all` : this.entityName + // var sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : '' + // var queryString = `?${sfQueryString}&limit=${this.booksPerFetch}&page=${page}` + + // if (this.entityName === 'series-books') { + // entityPath = `series/${this.seriesId}` + // queryString = '' + // } + + // var payload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/${entityPath}${queryString}`).catch((error) => { + // console.error('failed to fetch books', error) + // return null + // }) + + var entityPath = this.entityName === 'books' || this.entityName === 'series-books' ? `items` : this.entityName var sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : '' - var queryString = `?${sfQueryString}&limit=${this.booksPerFetch}&page=${page}` + var fullQueryString = `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1` - if (this.entityName === 'series-books') { - entityPath = `series/${this.seriesId}` - queryString = '' - } - - var payload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/${entityPath}${queryString}`).catch((error) => { + var payload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/${entityPath}${fullQueryString}`).catch((error) => { console.error('failed to fetch books', error) return null }) + this.isFetchingEntities = false if (this.pendingReset) { this.pendingReset = false @@ -390,42 +400,42 @@ export default { this.resetEntities() } }, - audiobookAdded(audiobook) { - console.log('Audiobook added', audiobook) - // TODO: Check if audiobook would be on this shelf + libraryItemAdded(libraryItem) { + console.log('libraryItem added', libraryItem) + // TODO: Check if item would be on this shelf this.resetEntities() }, - audiobookUpdated(audiobook) { - console.log('Audiobook updated', audiobook) + libraryItemUpdated(libraryItem) { + console.log('Item updated', libraryItem) if (this.entityName === 'books' || this.entityName === 'series-books') { - var indexOf = this.entities.findIndex((ent) => ent && ent.id === audiobook.id) + var indexOf = this.entities.findIndex((ent) => ent && ent.id === libraryItem.id) if (indexOf >= 0) { - this.entities[indexOf] = audiobook + this.entities[indexOf] = libraryItem if (this.entityComponentRefs[indexOf]) { - this.entityComponentRefs[indexOf].setEntity(audiobook) + this.entityComponentRefs[indexOf].setEntity(libraryItem) } } } }, - audiobookRemoved(audiobook) { + libraryItemRemoved(libraryItem) { if (this.entityName === 'books' || this.entityName === 'series-books') { - var indexOf = this.entities.findIndex((ent) => ent && ent.id === audiobook.id) + var indexOf = this.entities.findIndex((ent) => ent && ent.id === libraryItem.id) if (indexOf >= 0) { - this.entities = this.entities.filter((ent) => ent.id !== audiobook.id) + this.entities = this.entities.filter((ent) => ent.id !== libraryItem.id) this.totalEntities = this.entities.length this.$eventBus.$emit('bookshelf-total-entities', this.totalEntities) - this.remountEntities() + this.executeRebuild() } } }, - audiobooksAdded(audiobooks) { - console.log('audiobooks added', audiobooks) - // TODO: Check if audiobook would be on this shelf + libraryItemsAdded(libraryItems) { + console.log('items added', libraryItems) + // TODO: Check if item would be on this shelf this.resetEntities() }, - audiobooksUpdated(audiobooks) { - audiobooks.forEach((ab) => { - this.audiobookUpdated(ab) + libraryItemsUpdated(libraryItems) { + libraryItems.forEach((ab) => { + this.libraryItemUpdated(ab) }) }, initListeners() { @@ -433,19 +443,17 @@ export default { if (bookshelf) { bookshelf.addEventListener('scroll', this.scroll) } - // this.$eventBus.$on('bookshelf-clear-selection', this.clearSelectedEntities) - // this.$eventBus.$on('bookshelf-select-all', this.selectAllEntities) - // this.$eventBus.$on('bookshelf-keyword-filter', this.updateKeywordFilter) + this.$eventBus.$on('library-changed', this.libraryChanged) this.$eventBus.$on('downloads-loaded', this.downloadsLoaded) this.$store.commit('user/addSettingsListener', { id: 'lazy-bookshelf', meth: this.settingsUpdated }) if (this.$server.socket) { - this.$server.socket.on('audiobook_updated', this.audiobookUpdated) - this.$server.socket.on('audiobook_added', this.audiobookAdded) - this.$server.socket.on('audiobook_removed', this.audiobookRemoved) - this.$server.socket.on('audiobooks_updated', this.audiobooksUpdated) - this.$server.socket.on('audiobooks_added', this.audiobooksAdded) + this.$server.socket.on('item_updated', this.libraryItemUpdated) + this.$server.socket.on('item_added', this.libraryItemAdded) + this.$server.socket.on('item_removed', this.libraryItemRemoved) + this.$server.socket.on('items_updated', this.libraryItemsUpdated) + this.$server.socket.on('items_added', this.libraryItemsAdded) } else { console.error('Bookshelf - Socket not initialized') } @@ -455,16 +463,17 @@ export default { if (bookshelf) { bookshelf.removeEventListener('scroll', this.scroll) } + this.$eventBus.$off('library-changed', this.libraryChanged) this.$eventBus.$off('downloads-loaded', this.downloadsLoaded) this.$store.commit('user/removeSettingsListener', 'lazy-bookshelf') if (this.$server.socket) { - this.$server.socket.off('audiobook_updated', this.audiobookUpdated) - this.$server.socket.off('audiobook_added', this.audiobookAdded) - this.$server.socket.off('audiobook_removed', this.audiobookRemoved) - this.$server.socket.off('audiobooks_updated', this.audiobooksUpdated) - this.$server.socket.off('audiobooks_added', this.audiobooksAdded) + this.$server.socket.off('item_updated', this.libraryItemUpdated) + this.$server.socket.off('item_added', this.libraryItemAdded) + this.$server.socket.off('item_removed', this.libraryItemRemoved) + this.$server.socket.off('items_updated', this.libraryItemsUpdated) + this.$server.socket.off('items_added', this.libraryItemsAdded) } else { console.error('Bookshelf - Socket not initialized') } diff --git a/components/bookshelf/Shelf.vue b/components/bookshelf/Shelf.vue index 49abc225..2cd57b20 100644 --- a/components/bookshelf/Shelf.vue +++ b/components/bookshelf/Shelf.vue @@ -2,7 +2,7 @@
diff --git a/components/cards/LazyBookCard.vue b/components/cards/LazyBookCard.vue index 72ccc822..68b525d9 100644 --- a/components/cards/LazyBookCard.vue +++ b/components/cards/LazyBookCard.vue @@ -1,16 +1,27 @@ - \ No newline at end of file diff --git a/components/covers/CollectionCover.vue b/components/covers/CollectionCover.vue index 91eb1db7..403024eb 100644 --- a/components/covers/CollectionCover.vue +++ b/components/covers/CollectionCover.vue @@ -9,8 +9,8 @@
- - + +
diff --git a/components/covers/GroupCover.vue b/components/covers/GroupCover.vue index 20fd1439..8af50fd0 100644 --- a/components/covers/GroupCover.vue +++ b/components/covers/GroupCover.vue @@ -17,6 +17,7 @@ export default { }, width: Number, height: Number, + groupTo: String, bookCoverAspectRatio: Number }, data() { @@ -31,7 +32,6 @@ export default { isFannedOut: false, isDetached: false, isAttaching: false, - windowWidth: 0, isInit: false } }, @@ -48,8 +48,11 @@ export default { }, computed: { sizeMultiplier() { - if (this.bookCoverAspectRatio === 1) return this.width / (100 * 1.6 * 2) - return this.width / 200 + if (this.bookCoverAspectRatio === 1) return this.width / (120 * 1.6 * 2) + return this.width / 240 + }, + showExperimentalFeatures() { + return this.store.state.showExperimentalFeatures }, store() { return this.$store || this.$nuxt.$store @@ -59,44 +62,8 @@ export default { } }, methods: { - detchCoverWrapper() { - if (!this.coverWrapperEl || !this.$refs.wrapper || this.isDetached) return - - this.coverWrapperEl.remove() - - this.isDetached = true - document.body.appendChild(this.coverWrapperEl) - this.coverWrapperEl.addEventListener('mouseleave', this.mouseleaveCover) - - this.coverWrapperEl.style.position = 'absolute' - this.coverWrapperEl.style.zIndex = 40 - - this.updatePosition() - }, - attachCoverWrapper() { - if (!this.coverWrapperEl || !this.$refs.wrapper || !this.isDetached) return - - this.coverWrapperEl.remove() - this.coverWrapperEl.style.position = 'relative' - this.coverWrapperEl.style.left = 'unset' - this.coverWrapperEl.style.top = 'unset' - this.coverWrapperEl.style.width = this.$refs.wrapper.clientWidth + 'px' - - this.$refs.wrapper.appendChild(this.coverWrapperEl) - - this.isDetached = false - }, - updatePosition() { - var rect = this.$refs.wrapper.getBoundingClientRect() - this.coverWrapperEl.style.top = rect.top + window.scrollY + 'px' - - this.coverWrapperEl.style.left = rect.left + window.scrollX + 4 + 'px' - - this.coverWrapperEl.style.height = rect.height + 'px' - this.coverWrapperEl.style.width = rect.width + 'px' - }, getCoverUrl(book) { - return this.store.getters['audiobooks/getBookCoverSrc'](book, '') + return this.store.getters['globals/getLibraryItemCoverSrc'](book, '') }, async buildCoverImg(coverData, bgCoverWidth, offsetLeft, zIndex, forceCoverBg = false) { var src = coverData.coverUrl @@ -156,6 +123,22 @@ export default { imgdiv.appendChild(img) return imgdiv }, + createSeriesNameCover(offsetLeft) { + var imgdiv = document.createElement('div') + imgdiv.style.height = this.height + 'px' + imgdiv.style.width = this.height / this.bookCoverAspectRatio + 'px' + imgdiv.style.left = offsetLeft + 'px' + imgdiv.className = 'absolute top-0 box-shadow-book transition-transform flex items-center justify-center' + imgdiv.style.boxShadow = '4px 0px 4px #11111166' + imgdiv.style.backgroundColor = '#111' + + var innerP = document.createElement('p') + innerP.textContent = this.name + innerP.className = 'text-sm font-book text-white' + imgdiv.appendChild(innerP) + + return imgdiv + }, async init() { if (this.isInit) return this.isInit = true @@ -168,7 +151,6 @@ export default { .map((bookItem) => { return { id: bookItem.id, - volumeNumber: bookItem.book ? bookItem.book.volumeNumber : null, coverUrl: this.getCoverUrl(bookItem) } }) @@ -179,6 +161,8 @@ export default { } this.noValidCovers = false + validCovers = validCovers.slice(0, 10) + var coverWidth = this.width var widthPer = this.width if (validCovers.length > 1) { @@ -189,7 +173,7 @@ export default { this.offsetIncrement = widthPer var outerdiv = document.createElement('div') - outerdiv.id = `group-cover-${this.id || this.$encode(this.name)}` + outerdiv.id = `group-cover-${this.id}` this.coverWrapperEl = outerdiv outerdiv.className = 'w-full h-full relative box-shadow-book' @@ -211,9 +195,7 @@ export default { } } }, - mounted() { - this.windowWidth = window.innerWidth - }, + mounted() {}, beforeDestroy() { if (this.coverWrapperEl) this.coverWrapperEl.remove() if (this.coverImageEls && this.coverImageEls.length) { @@ -222,4 +204,4 @@ export default { if (this.coverDiv) this.coverDiv.remove() } } - \ No newline at end of file + diff --git a/components/widgets/LoadingSpinner.vue b/components/widgets/LoadingSpinner.vue new file mode 100644 index 00000000..07fdc83e --- /dev/null +++ b/components/widgets/LoadingSpinner.vue @@ -0,0 +1,241 @@ + + + + + \ No newline at end of file diff --git a/layouts/default.vue b/layouts/default.vue index 65cae499..0fdb58ce 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -51,7 +51,7 @@ export default { async connected(isConnected) { if (isConnected) { console.log('[Default] Connected socket sync user ab data') - this.$store.dispatch('user/syncUserAudiobookData') + // this.$store.dispatch('user/syncUserAudiobookData') this.initSocketListeners() @@ -326,51 +326,49 @@ export default { } } }, - audiobookAdded(audiobook) { - this.$store.commit('libraries/updateFilterDataWithAudiobook', audiobook) - }, - audiobookUpdated(audiobook) { - this.$store.commit('libraries/updateFilterDataWithAudiobook', audiobook) - }, - audiobookRemoved(audiobook) { - if (this.$route.name.startsWith('audiobook')) { - if (this.$route.params.id === audiobook.id) { + // audiobookAdded(audiobook) { + // this.$store.commit('libraries/updateFilterDataWithAudiobook', audiobook) + // }, + // audiobookUpdated(audiobook) { + // this.$store.commit('libraries/updateFilterDataWithAudiobook', audiobook) + // }, + itemRemoved(libraryItem) { + if (this.$route.name.startsWith('item')) { + if (this.$route.params.id === libraryItem.id) { this.$router.replace(`/bookshelf`) } } }, - audiobooksAdded(audiobooks) { - audiobooks.forEach((ab) => { - this.audiobookAdded(ab) - }) - }, - audiobooksUpdated(audiobooks) { - audiobooks.forEach((ab) => { - this.audiobookUpdated(ab) - }) - }, + // audiobooksAdded(audiobooks) { + // audiobooks.forEach((ab) => { + // this.audiobookAdded(ab) + // }) + // }, + // audiobooksUpdated(audiobooks) { + // audiobooks.forEach((ab) => { + // this.audiobookUpdated(ab) + // }) + // }, userLoggedOut() { // Only cancels stream if streamining not playing downloaded this.$eventBus.$emit('close_stream') }, initSocketListeners() { if (this.$server.socket) { - // Audiobook Listeners - this.$server.socket.on('audiobook_updated', this.audiobookUpdated) - this.$server.socket.on('audiobook_added', this.audiobookAdded) - this.$server.socket.on('audiobook_removed', this.audiobookRemoved) - this.$server.socket.on('audiobooks_updated', this.audiobooksUpdated) - this.$server.socket.on('audiobooks_added', this.audiobooksAdded) + // this.$server.socket.on('audiobook_updated', this.audiobookUpdated) + // this.$server.socket.on('audiobook_added', this.audiobookAdded) + this.$server.socket.on('item_removed', this.itemRemoved) + // this.$server.socket.on('audiobooks_updated', this.audiobooksUpdated) + // this.$server.socket.on('audiobooks_added', this.audiobooksAdded) } }, removeSocketListeners() { if (this.$server.socket) { - // Audiobook Listeners - this.$server.socket.off('audiobook_updated', this.audiobookUpdated) - this.$server.socket.off('audiobook_added', this.audiobookAdded) - this.$server.socket.off('audiobook_removed', this.audiobookRemoved) - this.$server.socket.off('audiobooks_updated', this.audiobooksUpdated) - this.$server.socket.off('audiobooks_added', this.audiobooksAdded) + // this.$server.socket.off('audiobook_updated', this.audiobookUpdated) + // this.$server.socket.off('audiobook_added', this.audiobookAdded) + this.$server.socket.off('item_removed', this.itemRemoved) + // this.$server.socket.off('audiobooks_updated', this.audiobooksUpdated) + // this.$server.socket.off('audiobooks_added', this.audiobooksAdded) } } }, @@ -382,6 +380,7 @@ export default { console.log('Syncing on default mount') this.connected(true) } + this.$server.on('logout', this.userLoggedOut) this.$server.on('connected', this.connected) this.$server.on('connectionFailed', this.socketConnectionFailed) diff --git a/mixins/bookshelfCardsHelpers.js b/mixins/bookshelfCardsHelpers.js index 0a66cc7a..811a6153 100644 --- a/mixins/bookshelfCardsHelpers.js +++ b/mixins/bookshelfCardsHelpers.js @@ -28,16 +28,7 @@ export default { if (this.entityComponentRefs[index]) { var bookComponent = this.entityComponentRefs[index] shelfEl.appendChild(bookComponent.$el) - if (this.isSelectionMode) { - bookComponent.setSelectionMode(true) - if (this.selectedAudiobooks.includes(bookComponent.audiobookId) || this.isSelectAll) { - bookComponent.selected = true - } else { - bookComponent.selected = false - } - } else { - bookComponent.setSelectionMode(false) - } + bookComponent.setSelectionMode(false) bookComponent.isHovering = false return } @@ -78,12 +69,6 @@ export default { if (this.entities[index]) { instance.setEntity(this.entities[index]) } - if (this.isSelectionMode) { - instance.setSelectionMode(true) - if (instance.audiobookId && this.selectedAudiobooks.includes(instance.audiobookId) || this.isSelectAll) { - instance.selected = true - } - } }, } } \ No newline at end of file diff --git a/pages/bookshelf/index.vue b/pages/bookshelf/index.vue index 3cf91544..3f2d6a59 100644 --- a/pages/bookshelf/index.vue +++ b/pages/bookshelf/index.vue @@ -122,7 +122,7 @@ export default { methods: { async fetchCategories() { var categories = await this.$axios - .$get(`/api/libraries/${this.currentLibraryId}/categories`) + .$get(`/api/libraries/${this.currentLibraryId}/personalized?minified=1`) .then((data) => { return data }) @@ -131,6 +131,7 @@ export default { return [] }) this.shelves = categories + console.log('Shelves', this.shelves) }, async socketInit(isConnected) { if (isConnected && this.currentLibraryId) { diff --git a/pages/connect.vue b/pages/connect.vue index 89be45a9..94e7b209 100644 --- a/pages/connect.vue +++ b/pages/connect.vue @@ -6,9 +6,9 @@
-

Audiobookshelf

+

audiobookshelf

- +

Important! This app requires that you are running your own server and does not provide any content.

@@ -18,7 +18,7 @@

Connecting socket..

-

Enter an Audiobookshelf
server address:

+

Server address

diff --git a/pages/item/_id.vue b/pages/item/_id.vue new file mode 100644 index 00000000..a2728566 --- /dev/null +++ b/pages/item/_id.vue @@ -0,0 +1,445 @@ + + + \ No newline at end of file diff --git a/store/globals.js b/store/globals.js new file mode 100644 index 00000000..0e8b924e --- /dev/null +++ b/store/globals.js @@ -0,0 +1,32 @@ +export const state = () => ({ + +}) + +export const getters = { + getLibraryItemCoverSrc: (state, getters, rootState, rootGetters) => (libraryItem, placeholder = '/book_placeholder.jpg') => { + if (!libraryItem) return placeholder + var media = libraryItem.media + if (!media || !media.coverPath || media.coverPath === placeholder) return placeholder + + // Absolute URL covers (should no longer be used) + if (media.coverPath.startsWith('http:') || media.coverPath.startsWith('https:')) return media.coverPath + + var userToken = rootGetters['user/getToken'] + var lastUpdate = libraryItem.updatedAt || Date.now() + + if (process.env.NODE_ENV !== 'production') { // Testing + // return `http://localhost:3333/api/items/${libraryItem.id}/cover?token=${userToken}&ts=${lastUpdate}` + } + + var url = new URL(`/api/items/${libraryItem.id}/cover`, rootState.serverUrl) + return `${url}?token=${userToken}&ts=${lastUpdate}` + } +} + +export const actions = { + +} + +export const mutations = { + +} \ No newline at end of file diff --git a/store/index.js b/store/index.js index 310efab1..0eba01d5 100644 --- a/store/index.js +++ b/store/index.js @@ -2,6 +2,7 @@ import Vue from 'vue' import { Network } from '@capacitor/network' export const state = () => ({ + streamLibraryItem: null, streamAudiobook: null, playingDownload: null, playOnLoad: false, @@ -80,6 +81,9 @@ export const mutations = { setPlayOnLoad(state, val) { state.playOnLoad = val }, + setLibraryItemStream(state, libraryItem) { + state.streamLibraryItem = libraryItem + }, setStreamAudiobook(state, audiobook) { if (audiobook) { state.playingDownload = null @@ -111,12 +115,6 @@ export const mutations = { state.networkConnected = val.connected state.networkConnectionType = val.connectionType }, - setStreamListener(state, val) { - state.streamListener = val - }, - removeStreamListener(state) { - state.streamListener = null - }, openReader(state, audiobook) { state.selectedBook = audiobook state.showReader = true diff --git a/store/user.js b/store/user.js index 01430f86..d632e288 100644 --- a/store/user.js +++ b/store/user.js @@ -20,6 +20,10 @@ export const getters = { getToken: (state) => { return state.user ? state.user.token : null }, + getUserLibraryItemProgress: (state) => (libraryItemId) => { + if (!state.user.libraryItemProgress) return null + return state.user.libraryItemProgress.find(li => li.id == libraryItemId) + }, getUserAudiobookData: (state, getters) => (audiobookId) => { return getters.getUserAudiobook(audiobookId) },