Lazy bookshelf finalized

This commit is contained in:
advplyr 2021-12-01 19:07:03 -06:00
parent 5c92aef048
commit 1ef9a689bc
53 changed files with 914 additions and 795 deletions

View file

@ -95,7 +95,7 @@
<span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">play_arrow</span>
{{ streaming ? 'Streaming' : 'Play' }}
</ui-btn>
<ui-btn v-else-if="isMissing || isIncomplete" color="error" :padding-x="4" small class="flex items-center h-9 mr-2">
<ui-btn v-else-if="isMissing || isInvalid" color="error" :padding-x="4" small class="flex items-center h-9 mr-2">
<span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">error</span>
{{ isMissing ? 'Missing' : 'Incomplete' }}
</ui-btn>
@ -169,7 +169,6 @@ export default {
console.error('No audiobook...', params.id)
return redirect('/')
}
store.commit('audiobooks/addUpdate', audiobook)
return {
audiobook
}
@ -234,11 +233,11 @@ export default {
isMissing() {
return this.audiobook.isMissing
},
isIncomplete() {
return this.audiobook.isIncomplete
isInvalid() {
return this.audiobook.isInvalid
},
showPlayButton() {
return !this.isMissing && !this.isIncomplete && this.tracks.length
return !this.isMissing && !this.isInvalid && this.tracks.length
},
missingParts() {
return this.audiobook.missingParts || []
@ -458,8 +457,8 @@ export default {
window.addEventListener('resize', this.resize)
this.$store.commit('audiobooks/addListener', { id: 'audiobook', audiobookId: this.audiobookId, meth: this.audiobookUpdated })
// If a library has not yet been loaded, use this audiobooks library id as the current
if (!this.$store.state.audiobooks.loadedLibraryId && this.libraryId) {
// use this audiobooks library id as the current
if (this.libraryId) {
this.$store.commit('libraries/setCurrentLibrary', this.libraryId)
}
},

View file

@ -62,11 +62,15 @@
<script>
export default {
asyncData({ store, redirect }) {
async asyncData({ store, redirect, app }) {
if (!store.state.selectedAudiobooks.length) {
return redirect('/')
}
var audiobooks = store.state.audiobooks.audiobooks.filter((ab) => store.state.selectedAudiobooks.includes(ab.id))
var audiobooks = await app.$axios.$post(`/api/books/batch/get`, { books: store.state.selectedAudiobooks }).catch((error) => {
var errorMsg = error.response.data || 'Failed to get audiobooks'
console.error(errorMsg, error)
return []
})
return {
audiobooks
}
@ -85,24 +89,27 @@ export default {
streamAudiobook() {
return this.$store.state.streamAudiobook
},
genres() {
return this.$store.state.audiobooks.genres
},
genreItems() {
return this.genres.concat(this.newGenreItems)
},
tags() {
return this.$store.state.audiobooks.tags
},
tagItems() {
return this.tags.concat(this.newTagItems)
},
series() {
return this.$store.state.audiobooks.series
},
seriesItems() {
return [...this.series, ...this.newSeriesItems]
},
genres() {
return this.filterData.genres || []
},
tags() {
return this.filterData.tags || []
},
series() {
return this.filterData.series || []
},
filterData() {
return this.$store.state.libraries.filterData || {}
},
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
}
@ -174,7 +181,7 @@ export default {
this.isProcessing = false
if (data.updates) {
this.$toast.success(`Successfully updated ${data.updates} audiobooks`)
this.$router.replace(`/library/${this.currentLibraryId}`)
this.$router.replace(`/library/${this.currentLibraryId}/bookshelf`)
} else {
this.$toast.warning('No updates were necessary')
}

View file

@ -83,7 +83,7 @@ export default {
},
playableBooks() {
return this.bookItems.filter((book) => {
return !book.isMissing && !book.isIncomplete && book.numTracks
return !book.isMissing && !book.isInvalid && book.numTracks
})
},
streaming() {

View file

@ -1,6 +1,6 @@
<template>
<div>
<stats-preview-icons :listening-stats="listeningStats" />
<stats-preview-icons :listening-stats="listeningStats" :library-stats="libraryStats" />
<div class="flex md:flex-row flex-wrap justify-between flex-col mt-12">
<div class="w-80 my-6 mx-auto">
@ -8,12 +8,12 @@
<template v-for="genre in top5Genres">
<div :key="genre.genre" class="w-full py-2">
<div class="flex items-end mb-1">
<p class="text-2xl font-bold">{{ Math.round((100 * genre.count) / audiobooks.length) }}&nbsp;%</p>
<p class="text-2xl font-bold">{{ Math.round((100 * genre.count) / totalBooks) }}&nbsp;%</p>
<div class="flex-grow" />
<p class="text-base font-book text-white text-opacity-70">{{ genre.genre }}</p>
</div>
<div class="w-full rounded-full h-3 bg-primary bg-opacity-50 overflow-hidden">
<div class="bg-yellow-400 h-full rounded-full" :style="{ width: Math.round((100 * genre.count) / audiobooks.length) + '%' }" />
<div class="bg-yellow-400 h-full rounded-full" :style="{ width: Math.round((100 * genre.count) / totalBooks) + '%' }" />
</div>
</div>
</template>
@ -43,49 +43,32 @@
export default {
data() {
return {
listeningStats: null
listeningStats: null,
libraryStats: null
}
},
watch: {
currentLibraryId(newVal, oldVal) {
if (newVal) {
this.init()
}
}
},
computed: {
audiobooks() {
return this.$store.state.audiobooks.audiobooks
},
user() {
return this.$store.state.user.user
},
totalBooks() {
return this.libraryStats ? this.libraryStats.totalBooks : 0
},
genresWithCount() {
var genresMap = {}
this.audiobooks.forEach((ab) => {
var genres = ab.book.genres || []
genres.forEach((genre) => {
if (genresMap[genre]) genresMap[genre].count++
else
genresMap[genre] = {
genre,
count: 1
}
})
})
var genres = Object.values(genresMap).sort((a, b) => b.count - a.count)
return genres
return this.libraryStats ? this.libraryStats.genresWithCount : []
},
top5Genres() {
return this.genresWithCount.slice(0, 5)
},
authorsWithCount() {
var authorsMap = {}
this.audiobooks.forEach((ab) => {
var authors = ab.book.authorFL ? ab.book.authorFL.split(', ') : []
authors.forEach((author) => {
if (authorsMap[author]) authorsMap[author].count++
else
authorsMap[author] = {
author,
count: 1
}
})
})
return Object.values(authorsMap).sort((a, b) => b.count - a.count)
return this.libraryStats ? this.libraryStats.authorsWithCount : []
},
mostUsedAuthorCount() {
if (!this.authorsWithCount.length) return 0
@ -93,10 +76,19 @@ export default {
},
top10Authors() {
return this.authorsWithCount.slice(0, 10)
},
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
}
},
methods: {
async init() {
this.libraryStats = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/stats`).catch((err) => {
console.error('Failed to get library stats', err)
var errorMsg = err.response ? err.response.data || 'Unknown Error' : 'Unknown Error'
this.$toast.error(`Failed to get library stats: ${errorMsg}`)
})
console.log('lib stats', this.libraryStats)
this.listeningStats = await this.$axios.$get(`/api/me/listening-stats`).catch((err) => {
console.error('Failed to load listening sesions', err)
return []
@ -106,7 +98,6 @@ export default {
},
mounted() {
this.init()
this.$store.dispatch('audiobooks/load')
}
}
</script>

View file

@ -1,13 +1,5 @@
<template>
<div class="page" :class="streamAudiobook ? 'streaming' : ''">
<!-- <div class="flex h-full">
<app-side-rail class="hidden md:block" />
<div class="flex-grow">
<app-book-shelf-toolbar is-home />
<app-book-shelf-categorized />
</div>
</div> -->
</div>
<div class="page" :class="streamAudiobook ? 'streaming' : ''"></div>
</template>
<script>

View file

@ -3,9 +3,8 @@
<div class="flex h-full">
<app-side-rail class="hidden md:block" />
<div class="flex-grow">
<app-book-shelf-toolbar :page="id || ''" :search-results="searchResults" :search-query="searchQuery" :selected-series.sync="selectedSeries" :view-mode.sync="viewMode" />
<app-lazy-bookshelf :page="id || ''" />
<!-- <app-book-shelf :page="id || ''" :search-results="searchResults" :search-query="searchQuery" :selected-series.sync="selectedSeries" :view-mode="viewMode" /> -->
<app-book-shelf-toolbar :page="id || ''" :view-mode.sync="viewMode" />
<app-lazy-bookshelf :page="id || ''" :view-mode="viewMode" />
</div>
</div>
</div>
@ -25,39 +24,13 @@ export default {
store.dispatch('user/updateUserSettings', { filterBy: query.filter })
}
// Search page
var searchResults = {}
var audiobookSearchResults = []
var searchQuery = null
if (params.id === 'search' && query.query) {
searchQuery = query.query
searchResults = await app.$axios.$get(`/api/libraries/${libraryId}/search?q=${searchQuery}`).catch((error) => {
console.error('Search error', error)
return {}
})
audiobookSearchResults = searchResults.audiobooks || []
store.commit('audiobooks/setSearchResults', searchResults)
if (audiobookSearchResults.length) audiobookSearchResults.forEach((ab) => store.commit('audiobooks/addUpdate', ab.audiobook))
}
// Series page
var selectedSeries = query.series ? app.$decode(query.series) : null
store.commit('audiobooks/setSelectedSeries', selectedSeries)
var libraryPage = params.id || ''
store.commit('audiobooks/setLibraryPage', libraryPage)
if (libraryPage === 'collections') {
store.dispatch('user/loadUserCollections')
}
// if (libraryPage === 'collections') {
// store.dispatch('user/loadUserCollections')
// }
return {
id: libraryPage,
libraryId,
searchQuery,
searchResults,
selectedSeries
id: params.id || '',
libraryId
}
},
data() {
@ -65,40 +38,11 @@ export default {
viewMode: 'grid'
}
},
watch: {
'$route.query'(newVal) {
if (this.id === 'search' && this.$route.query.query) {
if (this.$route.query.query !== this.searchQuery) {
this.newQuery()
}
} else if (this.id === 'series') {
if (this.selectedSeries && this.$route.query.series && this.$route.query.series !== this.$encode(this.selectedSeries)) {
// Series changed
this.selectedSeries = this.$decode(this.$route.query.series)
} else if (!this.selectedSeries && this.$route.query.series) {
// Series selected
this.selectedSeries = this.$decode(this.$route.query.series)
} else if (this.selectedSeries && !this.$route.query.series) {
// Series unselected
this.selectedSeries = null
}
}
}
},
computed: {
streamAudiobook() {
return this.$store.state.streamAudiobook
}
},
methods: {
async newQuery() {
var query = this.$route.query.query
this.searchResults = await this.$axios.$get(`/api/libraries/${this.libraryId}/search?q=${query}`).catch((error) => {
console.error('Search error', error)
return {}
})
this.searchQuery = query
}
}
methods: {}
}
</script>

View file

@ -3,8 +3,8 @@
<div class="flex h-full">
<app-side-rail class="hidden md:block" />
<div class="flex-grow">
<app-book-shelf-toolbar is-home />
<app-book-shelf-categorized v-if="hasResults" search :results="results" />
<app-book-shelf-toolbar is-home page="search" :search-query="query" />
<app-book-shelf-categorized v-if="hasResults" ref="bookshelf" search :results="results" />
<div v-else class="w-full py-16">
<p class="text-xl text-center">No Search results for "{{ query }}"</p>
<div class="flex justify-center">
@ -20,6 +20,10 @@
export default {
async asyncData({ store, params, redirect, query, app }) {
var libraryId = params.library
var library = await store.dispatch('libraries/fetch', libraryId)
if (!library) {
return redirect('/oops?message=Library not found')
}
var query = query.q
var results = await app.$axios.$get(`/api/libraries/${libraryId}/search?q=${query}`).catch((error) => {
console.error('Failed to search library', error)
@ -31,8 +35,8 @@ export default {
series: results && results.series.length ? results.series : null,
tags: results && results.tags.length ? results.tags : null
}
console.log('SEARCH RESULTS', results)
return {
libraryId,
results,
query
}
@ -40,6 +44,14 @@ export default {
data() {
return {}
},
watch: {
'$route.query'(newVal, oldVal) {
if (newVal && newVal.q && newVal.q !== this.query) {
this.query = newVal.q
this.search()
}
}
},
computed: {
streamAudiobook() {
return this.$store.state.streamAudiobook
@ -49,6 +61,23 @@ export default {
}
},
methods: {
async search() {
var results = await this.$axios.$get(`/api/libraries/${this.libraryId}/search?q=${this.query}`).catch((error) => {
console.error('Failed to search library', error)
return null
})
this.results = {
audiobooks: results && results.audiobooks.length ? results.audiobooks : null,
authors: results && results.authors.length ? results.authors : null,
series: results && results.series.length ? results.series : null,
tags: results && results.tags.length ? results.tags : null
}
this.$nextTick(() => {
if (this.$refs.bookshelf) {
this.$refs.bookshelf.setShelvesFromSearch()
}
})
},
async back() {
var popped = await this.$store.dispatch('popRoute')
if (popped) this.$store.commit('setIsRoutingBack', true)

View file

@ -0,0 +1,38 @@
<template>
<div class="page" :class="streamAudiobook ? 'streaming' : ''">
<div class="flex h-full">
<app-side-rail class="hidden md:block" />
<div class="flex-grow">
<app-book-shelf-toolbar :selected-series="series" />
<app-lazy-bookshelf page="series-books" :series-id="seriesId" />
</div>
</div>
</div>
</template>
<script>
export default {
async asyncData({ store, params, redirect, query, app }) {
var libraryId = params.library
var library = await store.dispatch('libraries/fetch', libraryId)
if (!library) {
return redirect('/oops?message=Library not found')
}
return {
series: app.$decode(params.id),
seriesId: params.id
}
},
data() {
return {}
},
computed: {
streamAudiobook() {
return this.$store.state.streamAudiobook
}
},
mounted() {},
beforeDestroy() {}
}
</script>