mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-04 11:04:46 +02:00
Finish lazy bookshelf,Fix:Async sql db,Fix:Load user progress
This commit is contained in:
parent
37d3021302
commit
3232b519d3
42 changed files with 613 additions and 1878 deletions
|
@ -155,6 +155,7 @@ class Server extends EventEmitter {
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.disconnect()
|
this.socket.disconnect()
|
||||||
}
|
}
|
||||||
|
this.emit('logout')
|
||||||
}
|
}
|
||||||
|
|
||||||
authorize(serverUrl, token) {
|
authorize(serverUrl, token) {
|
||||||
|
|
|
@ -57,7 +57,7 @@ class AudiobookManager {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = "$serverUrl/api/library/main/audiobooks"
|
var url = "$serverUrl/api/libraries/main/books/all"
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(url).addHeader("Authorization", "Bearer $token")
|
.url(url).addHeader("Authorization", "Bearer $token")
|
||||||
.build()
|
.build()
|
||||||
|
@ -74,11 +74,14 @@ class AudiobookManager {
|
||||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||||
|
|
||||||
var bodyString = response.body!!.string()
|
var bodyString = response.body!!.string()
|
||||||
var json = JSArray(bodyString)
|
var resJson = JSObject(bodyString)
|
||||||
var totalBooks = json.length() - 1
|
var results = resJson.getJSONArray("results")
|
||||||
|
|
||||||
|
var totalBooks = results.length() - 1
|
||||||
for (i in 0..totalBooks) {
|
for (i in 0..totalBooks) {
|
||||||
var abobj = json.get(i)
|
var abobj = results.get(i)
|
||||||
var jsobj = JSObject(abobj.toString())
|
var jsobj = JSObject(abobj.toString())
|
||||||
|
|
||||||
jsobj.put("isDownloaded", false)
|
jsobj.put("isDownloaded", false)
|
||||||
var audiobook = Audiobook(jsobj, serverUrl, token)
|
var audiobook = Audiobook(jsobj, serverUrl, token)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
@import "./fonts.css";
|
@import "./fonts.css";
|
||||||
|
|
||||||
|
#bookshelf {
|
||||||
|
min-height: calc(100vh - 48px);
|
||||||
|
}
|
||||||
|
|
||||||
.box-shadow-sm {
|
.box-shadow-sm {
|
||||||
box-shadow: 0px 3px 6px #11111170;
|
box-shadow: 0px 3px 6px #11111170;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,9 @@
|
||||||
</svg>
|
</svg>
|
||||||
<p class="text-lg font-book leading-4 ml-2">{{ currentLibraryName }}</p>
|
<p class="text-lg font-book leading-4 ml-2">{{ currentLibraryName }}</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- <p class="text-lg font-book leading-4">AudioBookshelf</p> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
|
|
||||||
<!-- <ui-menu :label="username" :items="menuItems" @action="menuAction" class="ml-5" /> -->
|
|
||||||
|
|
||||||
<!-- <span class="material-icons cursor-pointer mx-4" :class="hasDownloadsFolder ? '' : 'text-warning'" @click="$store.commit('downloads/setShowModal', true)">source</span> -->
|
|
||||||
|
|
||||||
<nuxt-link class="h-7 mx-2" to="/search">
|
<nuxt-link class="h-7 mx-2" to="/search">
|
||||||
<span class="material-icons" style="font-size: 1.75rem">search</span>
|
<span class="material-icons" style="font-size: 1.75rem">search</span>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
@ -36,19 +31,7 @@
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {}
|
||||||
menuItems: [
|
|
||||||
{
|
|
||||||
value: 'account',
|
|
||||||
text: 'Account',
|
|
||||||
to: '/account'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'logout',
|
|
||||||
text: 'Logout'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
socketConnected() {
|
socketConnected() {
|
||||||
|
@ -68,16 +51,6 @@ export default {
|
||||||
},
|
},
|
||||||
username() {
|
username() {
|
||||||
return this.user ? this.user.username : 'err'
|
return this.user ? this.user.username : 'err'
|
||||||
},
|
|
||||||
appListingUrl() {
|
|
||||||
if (this.$platform === 'android') {
|
|
||||||
return process.env.ANDROID_APP_URL
|
|
||||||
} else {
|
|
||||||
return process.env.IOS_APP_URL
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hasDownloadsFolder() {
|
|
||||||
return !!this.$store.state.downloadFolder
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -93,18 +66,6 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
this.$router.push('/bookshelf')
|
this.$router.push('/bookshelf')
|
||||||
}
|
}
|
||||||
},
|
|
||||||
logout() {
|
|
||||||
this.$axios.$post('/logout').catch((error) => {
|
|
||||||
console.error(error)
|
|
||||||
})
|
|
||||||
this.$server.logout()
|
|
||||||
this.$router.push('/connect')
|
|
||||||
},
|
|
||||||
menuAction(action) {
|
|
||||||
if (action === 'logout') {
|
|
||||||
this.logout()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
|
|
|
@ -12,9 +12,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cover-wrapper absolute z-30 pointer-events-auto" @click="clickContainer">
|
<div class="cover-wrapper absolute z-30 pointer-events-auto" :class="bookCoverAspectRatio === 1 ? 'square-cover' : ''" @click="clickContainer">
|
||||||
<div class="cover-container bookCoverWrapper bg-black bg-opacity-75 w-full h-full">
|
<div class="cover-container bookCoverWrapper bg-black bg-opacity-75 w-full h-full">
|
||||||
<cards-player-book-cover :audiobook="audiobook" :download-cover="downloadedCover" :width="showFullscreen ? 200 : 60" />
|
<covers-book-cover :audiobook="audiobook" :download-cover="downloadedCover" :width="bookCoverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -134,6 +134,15 @@ export default {
|
||||||
this.$emit('update:playing', val)
|
this.$emit('update:playing', val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
bookCoverAspectRatio() {
|
||||||
|
return this.$store.getters['getBookCoverAspectRatio']
|
||||||
|
},
|
||||||
|
bookCoverWidth() {
|
||||||
|
if (this.bookCoverAspectRatio === 1) {
|
||||||
|
return this.showFullscreen ? 260 : 60
|
||||||
|
}
|
||||||
|
return this.showFullscreen ? 200 : 60
|
||||||
|
},
|
||||||
book() {
|
book() {
|
||||||
return this.audiobook.book || {}
|
return this.audiobook.book || {}
|
||||||
},
|
},
|
||||||
|
@ -641,6 +650,9 @@ export default {
|
||||||
transition-property: left, bottom, width, height;
|
transition-property: left, bottom, width, height;
|
||||||
transform-origin: left bottom;
|
transform-origin: left bottom;
|
||||||
}
|
}
|
||||||
|
.cover-wrapper.square-cover {
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
.title-author-texts {
|
.title-author-texts {
|
||||||
transition: all 0.15s cubic-bezier(0.39, 0.575, 0.565, 1);
|
transition: all 0.15s cubic-bezier(0.39, 0.575, 0.565, 1);
|
||||||
|
@ -719,6 +731,12 @@ export default {
|
||||||
height: 320px;
|
height: 320px;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
.fullscreen .cover-wrapper.square-cover {
|
||||||
|
height: 260px;
|
||||||
|
width: 260px;
|
||||||
|
left: calc(50% - 130px);
|
||||||
|
}
|
||||||
|
|
||||||
.fullscreen #playerControls {
|
.fullscreen #playerControls {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
bottom: 100px;
|
bottom: 100px;
|
||||||
|
|
|
@ -113,9 +113,6 @@ export default {
|
||||||
cover() {
|
cover() {
|
||||||
return this.book ? this.book.cover : ''
|
return this.book ? this.book.cover : ''
|
||||||
},
|
},
|
||||||
downloadedCover() {
|
|
||||||
return this.download ? this.download.cover : null
|
|
||||||
},
|
|
||||||
series() {
|
series() {
|
||||||
return this.book ? this.book.series : ''
|
return this.book ? this.book.series : ''
|
||||||
},
|
},
|
||||||
|
@ -361,6 +358,7 @@ export default {
|
||||||
if (this.$refs.audioPlayer) {
|
if (this.$refs.audioPlayer) {
|
||||||
this.$refs.audioPlayer.terminateStream()
|
this.$refs.audioPlayer.terminateStream()
|
||||||
}
|
}
|
||||||
|
this.download = null
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastProgressTimeUpdate = 0
|
this.lastProgressTimeUpdate = 0
|
||||||
|
@ -450,6 +448,15 @@ export default {
|
||||||
this.$server.socket.on('stream_progress', this.streamProgress)
|
this.$server.socket.on('stream_progress', this.streamProgress)
|
||||||
this.$server.socket.on('stream_ready', this.streamReady)
|
this.$server.socket.on('stream_ready', this.streamReady)
|
||||||
this.$server.socket.on('stream_reset', this.streamReset)
|
this.$server.socket.on('stream_reset', this.streamReset)
|
||||||
|
},
|
||||||
|
closeStreamOnly() {
|
||||||
|
// If user logs out or disconnects from server, close audio if streaming
|
||||||
|
if (!this.download) {
|
||||||
|
this.$store.commit('setStreamAudiobook', null)
|
||||||
|
if (this.$refs.audioPlayer) {
|
||||||
|
this.$refs.audioPlayer.terminateStream()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -460,6 +467,7 @@ export default {
|
||||||
console.log(`[AudioPlayerContainer] Init Playback Speed: ${this.playbackSpeed}`)
|
console.log(`[AudioPlayerContainer] Init Playback Speed: ${this.playbackSpeed}`)
|
||||||
|
|
||||||
this.setListeners()
|
this.setListeners()
|
||||||
|
this.$eventBus.$on('close_stream', this.closeStreamOnly)
|
||||||
this.$store.commit('user/addSettingsListener', { id: 'streamContainer', meth: this.settingsUpdated })
|
this.$store.commit('user/addSettingsListener', { id: 'streamContainer', meth: this.settingsUpdated })
|
||||||
this.$store.commit('setStreamListener', this.streamUpdated)
|
this.$store.commit('setStreamListener', this.streamUpdated)
|
||||||
},
|
},
|
||||||
|
@ -475,6 +483,7 @@ export default {
|
||||||
this.$server.socket.off('stream_reset', this.streamReset)
|
this.$server.socket.off('stream_reset', this.streamReset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.$eventBus.$off('close_stream', this.closeStreamOnly)
|
||||||
this.$store.commit('user/removeSettingsListener', 'streamContainer')
|
this.$store.commit('user/removeSettingsListener', 'streamContainer')
|
||||||
this.$store.commit('removeStreamListener')
|
this.$store.commit('removeStreamListener')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,147 +0,0 @@
|
||||||
<template>
|
|
||||||
<div id="bookshelf" ref="wrapper" class="w-full overflow-y-auto">
|
|
||||||
<template v-for="(shelf, index) in groupedBooks">
|
|
||||||
<div :key="index" class="border-b border-opacity-10 w-full bookshelfRow py-4 flex justify-around relative">
|
|
||||||
<template v-for="audiobook in shelf">
|
|
||||||
<cards-book-card :key="audiobook.id" :audiobook="audiobook" :width="cardWidth" />
|
|
||||||
</template>
|
|
||||||
<div class="bookshelfDivider h-4 w-full absolute bottom-0 left-0 right-0 z-10" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div v-show="!groupedBooks.length" class="w-full py-16 text-center text-xl">
|
|
||||||
<div class="py-4">No Audiobooks</div>
|
|
||||||
<ui-btn v-if="hasFilters" @click="clearFilter">Clear Filter</ui-btn>
|
|
||||||
</div>
|
|
||||||
<div v-show="isLoading" class="absolute top-0 left-0 w-full h-full flex items-center justify-center bg-black bg-opacity-70 z-20">
|
|
||||||
<div class="py-4">Loading...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
currFilterOrderKey: null,
|
|
||||||
groupedBooks: [],
|
|
||||||
pageWidth: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isLoading() {
|
|
||||||
return this.$store.state.audiobooks.isLoading
|
|
||||||
},
|
|
||||||
cardWidth() {
|
|
||||||
return 140
|
|
||||||
},
|
|
||||||
cardHeight() {
|
|
||||||
return this.cardWidth * 2
|
|
||||||
},
|
|
||||||
filterOrderKey() {
|
|
||||||
return this.$store.getters['user/getFilterOrderKey']
|
|
||||||
},
|
|
||||||
hasFilters() {
|
|
||||||
return this.$store.getters['user/getUserSetting']('mobileFilterBy') !== 'all'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
clearFilter() {
|
|
||||||
this.$store.dispatch('user/updateUserSettings', {
|
|
||||||
mobileFilterBy: 'all'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
calcShelves() {
|
|
||||||
var booksPerShelf = Math.floor(this.pageWidth / (this.cardWidth + 32))
|
|
||||||
var groupedBooks = []
|
|
||||||
|
|
||||||
var audiobooksSorted = []
|
|
||||||
this.currFilterOrderKey = this.filterOrderKey
|
|
||||||
|
|
||||||
var numGroups = Math.ceil(audiobooksSorted.length / booksPerShelf)
|
|
||||||
for (let i = 0; i < numGroups; i++) {
|
|
||||||
var group = audiobooksSorted.slice(i * booksPerShelf, i * booksPerShelf + 2)
|
|
||||||
groupedBooks.push(group)
|
|
||||||
}
|
|
||||||
this.groupedBooks = groupedBooks
|
|
||||||
},
|
|
||||||
audiobooksUpdated() {
|
|
||||||
this.calcShelves()
|
|
||||||
},
|
|
||||||
init() {
|
|
||||||
if (this.$refs.wrapper) {
|
|
||||||
this.pageWidth = this.$refs.wrapper.clientWidth
|
|
||||||
this.calcShelves()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resize() {
|
|
||||||
this.init()
|
|
||||||
},
|
|
||||||
settingsUpdated() {
|
|
||||||
if (this.currFilterOrderKey !== this.filterOrderKey) {
|
|
||||||
this.calcShelves()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async loadAudiobooks() {
|
|
||||||
var currentLibrary = await this.$localStore.getCurrentLibrary()
|
|
||||||
if (currentLibrary) {
|
|
||||||
this.$store.commit('libraries/setCurrentLibrary', currentLibrary.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
socketConnected(isConnected) {
|
|
||||||
if (isConnected) {
|
|
||||||
console.log('Connected - Load from server')
|
|
||||||
this.loadAudiobooks()
|
|
||||||
} else {
|
|
||||||
console.log('Disconnected - Reset to local storage')
|
|
||||||
this.$store.commit('audiobooks/reset')
|
|
||||||
this.$store.dispatch('audiobooks/useDownloaded')
|
|
||||||
// this.calcShelves()
|
|
||||||
// this.$store.dispatch('downloads/loadFromStorage')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.$store.commit('audiobooks/addListener', { id: 'bookshelf', meth: this.audiobooksUpdated })
|
|
||||||
this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated })
|
|
||||||
window.addEventListener('resize', this.resize)
|
|
||||||
|
|
||||||
if (!this.$server) {
|
|
||||||
console.error('Bookshelf mounted no server')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$server.on('connected', this.socketConnected)
|
|
||||||
if (this.$server.connected) {
|
|
||||||
this.loadAudiobooks()
|
|
||||||
} else {
|
|
||||||
console.log('Bookshelf - Server not connected using downloaded')
|
|
||||||
}
|
|
||||||
this.init()
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.$store.commit('audiobooks/removeListener', 'bookshelf')
|
|
||||||
this.$store.commit('user/removeSettingsListener', 'bookshelf')
|
|
||||||
window.removeEventListener('resize', this.resize)
|
|
||||||
|
|
||||||
if (!this.$server) {
|
|
||||||
console.error('Bookshelf beforeDestroy no server')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.$server.off('connected', this.socketConnected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#bookshelf {
|
|
||||||
height: calc(100% - 48px);
|
|
||||||
}
|
|
||||||
.bookshelfRow {
|
|
||||||
background-image: url(/wood_panels.jpg);
|
|
||||||
}
|
|
||||||
.bookshelfDivider {
|
|
||||||
background: rgb(149, 119, 90);
|
|
||||||
background: linear-gradient(180deg, rgba(149, 119, 90, 1) 0%, rgba(103, 70, 37, 1) 17%, rgba(103, 70, 37, 1) 88%, rgba(71, 48, 25, 1) 100%);
|
|
||||||
box-shadow: 2px 14px 8px #111111aa;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,135 +0,0 @@
|
||||||
<template>
|
|
||||||
<div id="bookshelf" ref="wrapper" class="w-full overflow-y-auto">
|
|
||||||
<template v-for="(ab, index) in audiobooks">
|
|
||||||
<div :key="index" class="border-b border-opacity-10 w-full bookshelfRow py-4 px-2 flex relative">
|
|
||||||
<app-bookshelf-list-row :audiobook="ab" :card-width="cardWidth" :page-width="pageWidth" />
|
|
||||||
<div class="bookshelfDivider h-4 w-full absolute bottom-0 left-0 right-0 z-10" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div v-show="!audiobooks.length" class="w-full py-16 text-center text-xl">
|
|
||||||
<div class="py-4">No Audiobooks</div>
|
|
||||||
<ui-btn v-if="hasFilters" @click="clearFilter">Clear Filter</ui-btn>
|
|
||||||
</div>
|
|
||||||
<div v-show="isLoading" class="absolute top-0 left-0 w-full h-full flex items-center justify-center bg-black bg-opacity-70 z-20">
|
|
||||||
<div class="py-4">Loading...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
currFilterOrderKey: null,
|
|
||||||
pageWidth: 0,
|
|
||||||
audiobooks: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isLoading() {
|
|
||||||
return this.$store.state.audiobooks.isLoading
|
|
||||||
},
|
|
||||||
cardWidth() {
|
|
||||||
return 75
|
|
||||||
},
|
|
||||||
cardHeight() {
|
|
||||||
return this.cardWidth * 2
|
|
||||||
},
|
|
||||||
contentRowWidth() {
|
|
||||||
return this.pageWidth - 16 - this.cardWidth
|
|
||||||
},
|
|
||||||
filterOrderKey() {
|
|
||||||
return this.$store.getters['user/getFilterOrderKey']
|
|
||||||
},
|
|
||||||
hasFilters() {
|
|
||||||
return this.$store.getters['user/getUserSetting']('mobileFilterBy') !== 'all'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
clearFilter() {
|
|
||||||
this.$store.dispatch('user/updateUserSettings', {
|
|
||||||
mobileFilterBy: 'all'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
calcShelves() {},
|
|
||||||
audiobooksUpdated() {
|
|
||||||
this.calcShelves()
|
|
||||||
},
|
|
||||||
init() {
|
|
||||||
if (this.$refs.wrapper) {
|
|
||||||
this.pageWidth = this.$refs.wrapper.clientWidth
|
|
||||||
this.calcShelves()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resize() {
|
|
||||||
this.init()
|
|
||||||
},
|
|
||||||
settingsUpdated() {
|
|
||||||
if (this.currFilterOrderKey !== this.filterOrderKey) {
|
|
||||||
this.calcShelves()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async loadAudiobooks() {
|
|
||||||
var currentLibrary = await this.$localStore.getCurrentLibrary()
|
|
||||||
if (currentLibrary) {
|
|
||||||
this.$store.commit('libraries/setCurrentLibrary', currentLibrary.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
socketConnected(isConnected) {
|
|
||||||
if (isConnected) {
|
|
||||||
console.log('Connected - Load from server')
|
|
||||||
this.loadAudiobooks()
|
|
||||||
} else {
|
|
||||||
console.log('Disconnected - Reset to local storage')
|
|
||||||
this.$store.commit('audiobooks/reset')
|
|
||||||
this.$store.dispatch('audiobooks/useDownloaded')
|
|
||||||
// this.calcShelves()
|
|
||||||
// this.$store.dispatch('downloads/loadFromStorage')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.$store.commit('audiobooks/addListener', { id: 'bookshelf', meth: this.audiobooksUpdated })
|
|
||||||
this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated })
|
|
||||||
window.addEventListener('resize', this.resize)
|
|
||||||
|
|
||||||
if (!this.$server) {
|
|
||||||
console.error('Bookshelf mounted no server')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$server.on('connected', this.socketConnected)
|
|
||||||
if (this.$server.connected) {
|
|
||||||
this.loadAudiobooks()
|
|
||||||
} else {
|
|
||||||
console.log('Bookshelf - Server not connected using downloaded')
|
|
||||||
}
|
|
||||||
this.init()
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.$store.commit('audiobooks/removeListener', 'bookshelf')
|
|
||||||
this.$store.commit('user/removeSettingsListener', 'bookshelf')
|
|
||||||
window.removeEventListener('resize', this.resize)
|
|
||||||
|
|
||||||
if (!this.$server) {
|
|
||||||
console.error('Bookshelf beforeDestroy no server')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.$server.off('connected', this.socketConnected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#bookshelf {
|
|
||||||
height: calc(100% - 48px);
|
|
||||||
}
|
|
||||||
.bookshelfRow {
|
|
||||||
background-image: url(/wood_panels.jpg);
|
|
||||||
}
|
|
||||||
.bookshelfDivider {
|
|
||||||
background: rgb(149, 119, 90);
|
|
||||||
background: linear-gradient(180deg, rgba(149, 119, 90, 1) 0%, rgba(103, 70, 37, 1) 17%, rgba(103, 70, 37, 1) 88%, rgba(71, 48, 25, 1) 100%);
|
|
||||||
box-shadow: 2px 14px 8px #111111aa;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,152 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="w-full h-full flex">
|
|
||||||
<cards-book-card :audiobook="audiobook" :width="cardWidth" class="self-end" />
|
|
||||||
<div class="relative px-2" :style="{ width: contentRowWidth + 'px' }">
|
|
||||||
<div class="flex">
|
|
||||||
<nuxt-link :to="`/audiobook/${audiobook.id}`">
|
|
||||||
<p class="leading-6" style="font-size: 1.1rem">{{ audiobook.book.title }}</p>
|
|
||||||
</nuxt-link>
|
|
||||||
<div class="flex-grow" />
|
|
||||||
<div class="flex items-center">
|
|
||||||
<!-- <button class="mx-1" @click="editAudiobook(ab)">
|
|
||||||
<span class="material-icons text-icon pb-px">edit</span>
|
|
||||||
</button> -->
|
|
||||||
<button v-if="showRead" class="mx-1 rounded-full w-6 h-6" @click="readBook">
|
|
||||||
<span class="material-icons">auto_stories</span>
|
|
||||||
</button>
|
|
||||||
<button v-if="showPlay" class="mx-1 rounded-full w-6 h-6" @click="playAudiobook">
|
|
||||||
<span class="material-icons">play_arrow</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p v-if="audiobook.book.subtitle" class="text-gray-200 leading-6 truncate" style="font-size: 0.9rem">{{ audiobook.book.subtitle }}</p>
|
|
||||||
<p class="text-sm text-gray-200">by {{ audiobook.book.author }}</p>
|
|
||||||
<div v-if="numTracks" class="flex items-center py-1">
|
|
||||||
<p class="text-xs text-gray-300">{{ $elapsedPretty(audiobook.duration) }}</p>
|
|
||||||
<span class="px-3 text-xs text-gray-300">•</span>
|
|
||||||
<p class="text-xs text-gray-300 font-mono">{{ $bytesPretty(audiobook.size, 0) }}</p>
|
|
||||||
<span class="px-3 text-xs text-gray-300">•</span>
|
|
||||||
<p class="text-xs text-gray-300">{{ numTracks }} tracks</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex">
|
|
||||||
<div v-if="userProgressPercent && !userIsRead" class="w-min my-1">
|
|
||||||
<div class="bg-primary bg-opacity-70 text-sm px-2 py-px rounded-full whitespace-nowrap">Progress: {{ Math.floor(userProgressPercent * 100) }}%</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="isDownloadPlayable" class="w-min my-1 mx-1">
|
|
||||||
<div class="bg-success bg-opacity-70 text-sm px-2 py-px rounded-full whitespace-nowrap">Downloaded</div>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="isDownloading" class="w-min my-1 mx-1">
|
|
||||||
<div class="bg-warning bg-opacity-70 text-sm px-2 py-px rounded-full whitespace-nowrap">Downloading...</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="isPlaying" class="w-min my-1 mx-1">
|
|
||||||
<div class="bg-info bg-opacity-70 text-sm px-2 py-px rounded-full whitespace-nowrap">{{ isStreaming ? 'Streaming' : 'Playing' }}</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="hasEbook" class="w-min my-1 mx-1">
|
|
||||||
<div class="bg-bg bg-opacity-70 text-sm px-2 py-px rounded-full whitespace-nowrap">{{ ebookFormat }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
audiobook: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
},
|
|
||||||
cardWidth: {
|
|
||||||
type: Number,
|
|
||||||
default: 75
|
|
||||||
},
|
|
||||||
pageWidth: Number
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
audiobookId() {
|
|
||||||
return this.audiobook.id
|
|
||||||
},
|
|
||||||
mostRecentUserProgress() {
|
|
||||||
return this.$store.getters['user/getUserAudiobookData'](this.audiobookId)
|
|
||||||
},
|
|
||||||
userProgressPercent() {
|
|
||||||
return this.mostRecentUserProgress ? this.mostRecentUserProgress.progress || 0 : 0
|
|
||||||
},
|
|
||||||
userIsRead() {
|
|
||||||
return this.mostRecentUserProgress ? !!this.mostRecentUserProgress.isRead : false
|
|
||||||
},
|
|
||||||
contentRowWidth() {
|
|
||||||
return this.pageWidth - 16 - this.cardWidth
|
|
||||||
},
|
|
||||||
isDownloading() {
|
|
||||||
return this.downloadObj ? this.downloadObj.isDownloading : false
|
|
||||||
},
|
|
||||||
isDownloadPreparing() {
|
|
||||||
return this.downloadObj ? this.downloadObj.isPreparing : false
|
|
||||||
},
|
|
||||||
isDownloadPlayable() {
|
|
||||||
return this.downloadObj && !this.isDownloading && !this.isDownloadPreparing
|
|
||||||
},
|
|
||||||
downloadedCover() {
|
|
||||||
return this.downloadObj ? this.downloadObj.cover : null
|
|
||||||
},
|
|
||||||
downloadObj() {
|
|
||||||
return this.$store.getters['downloads/getDownload'](this.audiobookId)
|
|
||||||
},
|
|
||||||
isStreaming() {
|
|
||||||
return this.$store.getters['isAudiobookStreaming'](this.audiobookId)
|
|
||||||
},
|
|
||||||
isPlaying() {
|
|
||||||
return this.$store.getters['isAudiobookPlaying'](this.audiobookId)
|
|
||||||
},
|
|
||||||
isMissing() {
|
|
||||||
return this.audiobook.isMissing
|
|
||||||
},
|
|
||||||
isIncomplete() {
|
|
||||||
return this.audiobook.isIncomplete
|
|
||||||
},
|
|
||||||
numTracks() {
|
|
||||||
if (this.audiobook.tracks) return this.audiobook.tracks.length
|
|
||||||
return this.audiobook.numTracks || 0
|
|
||||||
},
|
|
||||||
showPlay() {
|
|
||||||
return !this.isPlaying && !this.isMissing && !this.isIncomplete && this.numTracks
|
|
||||||
},
|
|
||||||
showRead() {
|
|
||||||
return this.hasEbook && this.ebookFormat !== '.pdf'
|
|
||||||
},
|
|
||||||
hasEbook() {
|
|
||||||
return this.audiobook.numEbooks
|
|
||||||
},
|
|
||||||
ebookFormat() {
|
|
||||||
if (!this.audiobook || !this.audiobook.ebooks || !this.audiobook.ebooks.length) return null
|
|
||||||
return this.audiobook.ebooks[0].ext.substr(1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
readBook() {
|
|
||||||
this.$store.commit('openReader', this.audiobook)
|
|
||||||
},
|
|
||||||
playAudiobook() {
|
|
||||||
if (this.isPlaying) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.$store.commit('setPlayOnLoad', true)
|
|
||||||
if (!this.isDownloadPlayable) {
|
|
||||||
// Stream
|
|
||||||
console.log('[PLAYCLICK] Set Playing STREAM ' + this.audiobook.book.title)
|
|
||||||
this.$store.commit('setStreamAudiobook', this.audiobook)
|
|
||||||
this.$server.socket.emit('open_stream', this.audiobook.id)
|
|
||||||
} else {
|
|
||||||
// Local
|
|
||||||
console.log('[PLAYCLICK] Set Playing Local Download ' + this.audiobook.book.title)
|
|
||||||
this.$store.commit('setPlayingDownload', this.downloadObj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -113,9 +113,6 @@ export default {
|
||||||
})
|
})
|
||||||
this.$server.logout()
|
this.$server.logout()
|
||||||
this.$router.push('/connect')
|
this.$router.push('/connect')
|
||||||
|
|
||||||
this.$store.commit('audiobooks/reset')
|
|
||||||
this.$store.dispatch('audiobooks/useDownloaded')
|
|
||||||
},
|
},
|
||||||
touchstart(e) {
|
touchstart(e) {
|
||||||
this.touchEvent = new TouchEvent(e)
|
this.touchEvent = new TouchEvent(e)
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="w-full relative">
|
|
||||||
<div class="bookshelfRow flex items-end justify-around px-3 max-w-full" :class="shelfHeightClass">
|
|
||||||
<template v-for="group in groups">
|
|
||||||
<cards-series-card v-if="groupType === 'series'" :key="group.id" :group="group" :width="112" class="mx-2" />
|
|
||||||
<cards-collection-card v-if="groupType === 'collection'" :key="group.id" :collection="group" :width="90" class="mx-2" />
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w-full h-5 z-40 bookshelfDivider"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
groupType: String,
|
|
||||||
groups: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
shelfHeightClass() {
|
|
||||||
if (this.groupType === 'series') return 'h-48'
|
|
||||||
return 'h-44'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {},
|
|
||||||
mounted() {}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,16 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="bookshelf" class="w-full max-w-full h-full min-h-screen">
|
<div id="bookshelf" class="w-full max-w-full h-full">
|
||||||
<template v-for="shelf in totalShelves">
|
<template v-for="shelf in totalShelves">
|
||||||
<div :key="shelf" class="w-full px-2 bookshelfRow relative" :id="`shelf-${shelf - 1}`" :style="{ height: shelfHeight + 'px' }">
|
<div :key="shelf" class="w-full px-2 bookshelfRow relative" :id="`shelf-${shelf - 1}`" :style="{ height: shelfHeight + 'px' }">
|
||||||
<div class="bookshelfDivider w-full absolute bottom-0 left-0 right-0 z-20" :class="`h-${shelfDividerHeightIndex}`" />
|
<div class="bookshelfDivider w-full absolute bottom-0 left-0 z-30" style="min-height: 16px" :class="`h-${shelfDividerHeightIndex}`" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- <div class="w-full h-full px-4 py-2" v-show="isListView">
|
|
||||||
<template v-for="book in books">
|
|
||||||
<app-bookshelf-list-row :key="book.id" :audiobook="book" :page-width="pageWidth" class="my-2" />
|
|
||||||
</template>
|
|
||||||
</div> -->
|
|
||||||
<div v-show="!entities.length && initialized" class="w-full py-16 text-center text-xl">
|
<div v-show="!entities.length && initialized" class="w-full py-16 text-center text-xl">
|
||||||
<div class="py-4 capitalize">No {{ entityName }}</div>
|
<div class="py-4 capitalize">No {{ entityName }}</div>
|
||||||
<ui-btn v-if="hasFilter" @click="clearFilter">Clear Filter</ui-btn>
|
<ui-btn v-if="hasFilter" @click="clearFilter">Clear Filter</ui-btn>
|
||||||
|
@ -121,8 +116,17 @@ export default {
|
||||||
// Includes margin
|
// Includes margin
|
||||||
return this.entityWidth + 24
|
return this.entityWidth + 24
|
||||||
},
|
},
|
||||||
|
downloads() {
|
||||||
|
return this.$store.getters['downloads/getDownloads']
|
||||||
|
},
|
||||||
downloadedBooks() {
|
downloadedBooks() {
|
||||||
return this.$store.getters['downloads/getAudiobooks']
|
return this.downloads.map((dl) => {
|
||||||
|
var download = { ...dl }
|
||||||
|
var ab = { ...download.audiobook }
|
||||||
|
delete download.audiobook
|
||||||
|
ab.download = download
|
||||||
|
return ab
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -169,6 +173,16 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < payload.results.length; i++) {
|
for (let i = 0; i < payload.results.length; i++) {
|
||||||
|
if (this.entityName === 'books' || this.entityName === 'series-books') {
|
||||||
|
// Check if has download and append download obj
|
||||||
|
var download = this.downloads.find((dl) => dl.id === payload.results[i].id)
|
||||||
|
if (download) {
|
||||||
|
var dl = { ...download }
|
||||||
|
delete dl.audiobook
|
||||||
|
payload.results[i].download = dl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var index = i + startIndex
|
var index = i + startIndex
|
||||||
this.entities[index] = payload.results[i]
|
this.entities[index] = payload.results[i]
|
||||||
if (this.entityComponentRefs[index]) {
|
if (this.entityComponentRefs[index]) {
|
||||||
|
@ -227,12 +241,17 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setDownloads() {
|
setDownloads() {
|
||||||
// TODO: Check entityName
|
if (this.entityName === 'books') {
|
||||||
this.entities = this.downloadedBooks.map((db) => ({ ...db }))
|
this.entities = this.downloadedBooks
|
||||||
// TOOD: Sort and filter here
|
// TOOD: Sort and filter here
|
||||||
this.totalEntities = this.entities.length
|
this.totalEntities = this.entities.length
|
||||||
this.totalShelves = Math.ceil(this.totalEntities / this.entitiesPerShelf)
|
this.totalShelves = Math.ceil(this.totalEntities / this.entitiesPerShelf)
|
||||||
this.entities = new Array(this.totalEntities)
|
} else {
|
||||||
|
// TODO: Support offline series and collections
|
||||||
|
this.entities = []
|
||||||
|
this.totalEntities = 0
|
||||||
|
this.totalShelves = 0
|
||||||
|
}
|
||||||
this.$eventBus.$emit('bookshelf-total-entities', this.totalEntities)
|
this.$eventBus.$emit('bookshelf-total-entities', this.totalEntities)
|
||||||
},
|
},
|
||||||
async resetEntities() {
|
async resetEntities() {
|
||||||
|
@ -257,9 +276,22 @@ export default {
|
||||||
this.mountEntites(0, lastBookIndex)
|
this.mountEntites(0, lastBookIndex)
|
||||||
} else {
|
} else {
|
||||||
this.setDownloads()
|
this.setDownloads()
|
||||||
|
|
||||||
this.mountEntites(0, this.totalEntities - 1)
|
this.mountEntites(0, this.totalEntities - 1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
remountEntities() {
|
||||||
|
// Remount when an entity is removed
|
||||||
|
for (const key in this.entityComponentRefs) {
|
||||||
|
if (this.entityComponentRefs[key]) {
|
||||||
|
this.entityComponentRefs[key].destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.entityComponentRefs = {}
|
||||||
|
this.entityIndexesMounted.forEach((i) => {
|
||||||
|
this.cardsHelpers.mountEntityCard(i)
|
||||||
|
})
|
||||||
|
},
|
||||||
initSizeData() {
|
initSizeData() {
|
||||||
var bookshelf = document.getElementById('bookshelf')
|
var bookshelf = document.getElementById('bookshelf')
|
||||||
if (!bookshelf) {
|
if (!bookshelf) {
|
||||||
|
@ -282,15 +314,23 @@ export default {
|
||||||
}
|
}
|
||||||
return entitiesPerShelfBefore < this.entitiesPerShelf // Books per shelf has changed
|
return entitiesPerShelfBefore < this.entitiesPerShelf // Books per shelf has changed
|
||||||
},
|
},
|
||||||
async init(bookshelf) {
|
async init() {
|
||||||
if (this.isFirstInit) return
|
if (this.isFirstInit) return
|
||||||
this.isFirstInit = true
|
this.isFirstInit = true
|
||||||
this.initSizeData(bookshelf)
|
this.initSizeData()
|
||||||
|
|
||||||
await this.loadPage(0)
|
await this.loadPage(0)
|
||||||
var lastBookIndex = Math.min(this.totalEntities, this.shelvesPerPage * this.entitiesPerShelf)
|
var lastBookIndex = Math.min(this.totalEntities, this.shelvesPerPage * this.entitiesPerShelf)
|
||||||
this.mountEntites(0, lastBookIndex)
|
this.mountEntites(0, lastBookIndex)
|
||||||
},
|
},
|
||||||
|
initDownloads() {
|
||||||
|
this.initSizeData()
|
||||||
|
this.setDownloads()
|
||||||
|
this.$nextTick(() => {
|
||||||
|
console.log('Mounting downloads', this.totalEntities, 'total shelves', this.totalShelves)
|
||||||
|
this.mountEntites(0, this.totalEntities)
|
||||||
|
})
|
||||||
|
},
|
||||||
scroll(e) {
|
scroll(e) {
|
||||||
if (!e || !e.target) return
|
if (!e || !e.target) return
|
||||||
if (!this.isSocketConnected) return // Offline books are all mounted at once
|
if (!this.isSocketConnected) return // Offline books are all mounted at once
|
||||||
|
@ -345,6 +385,49 @@ export default {
|
||||||
this.resetEntities()
|
this.resetEntities()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
downloadsLoaded() {
|
||||||
|
if (!this.isSocketConnected) {
|
||||||
|
this.resetEntities()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
audiobookAdded(audiobook) {
|
||||||
|
console.log('Audiobook added', audiobook)
|
||||||
|
// TODO: Check if audiobook would be on this shelf
|
||||||
|
this.resetEntities()
|
||||||
|
},
|
||||||
|
audiobookUpdated(audiobook) {
|
||||||
|
console.log('Audiobook updated', audiobook)
|
||||||
|
if (this.entityName === 'books' || this.entityName === 'series-books') {
|
||||||
|
var indexOf = this.entities.findIndex((ent) => ent && ent.id === audiobook.id)
|
||||||
|
if (indexOf >= 0) {
|
||||||
|
this.entities[indexOf] = audiobook
|
||||||
|
if (this.entityComponentRefs[indexOf]) {
|
||||||
|
this.entityComponentRefs[indexOf].setEntity(audiobook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
audiobookRemoved(audiobook) {
|
||||||
|
if (this.entityName === 'books' || this.entityName === 'series-books') {
|
||||||
|
var indexOf = this.entities.findIndex((ent) => ent && ent.id === audiobook.id)
|
||||||
|
if (indexOf >= 0) {
|
||||||
|
this.entities = this.entities.filter((ent) => ent.id !== audiobook.id)
|
||||||
|
this.totalEntities = this.entities.length
|
||||||
|
this.$eventBus.$emit('bookshelf-total-entities', this.totalEntities)
|
||||||
|
this.remountEntities()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
audiobooksAdded(audiobooks) {
|
||||||
|
console.log('audiobooks added', audiobooks)
|
||||||
|
// TODO: Check if audiobook would be on this shelf
|
||||||
|
this.resetEntities()
|
||||||
|
},
|
||||||
|
audiobooksUpdated(audiobooks) {
|
||||||
|
audiobooks.forEach((ab) => {
|
||||||
|
this.audiobookUpdated(ab)
|
||||||
|
})
|
||||||
|
},
|
||||||
initListeners() {
|
initListeners() {
|
||||||
var bookshelf = document.getElementById('bookshelf-wrapper')
|
var bookshelf = document.getElementById('bookshelf-wrapper')
|
||||||
if (bookshelf) {
|
if (bookshelf) {
|
||||||
|
@ -354,17 +437,18 @@ export default {
|
||||||
// this.$eventBus.$on('bookshelf-select-all', this.selectAllEntities)
|
// this.$eventBus.$on('bookshelf-select-all', this.selectAllEntities)
|
||||||
// this.$eventBus.$on('bookshelf-keyword-filter', this.updateKeywordFilter)
|
// this.$eventBus.$on('bookshelf-keyword-filter', this.updateKeywordFilter)
|
||||||
this.$eventBus.$on('library-changed', this.libraryChanged)
|
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 })
|
this.$store.commit('user/addSettingsListener', { id: 'lazy-bookshelf', meth: this.settingsUpdated })
|
||||||
|
|
||||||
// if (this.$root.socket) {
|
if (this.$server.socket) {
|
||||||
// this.$root.socket.on('audiobook_updated', this.audiobookUpdated)
|
this.$server.socket.on('audiobook_updated', this.audiobookUpdated)
|
||||||
// this.$root.socket.on('audiobook_added', this.audiobookAdded)
|
this.$server.socket.on('audiobook_added', this.audiobookAdded)
|
||||||
// this.$root.socket.on('audiobook_removed', this.audiobookRemoved)
|
this.$server.socket.on('audiobook_removed', this.audiobookRemoved)
|
||||||
// this.$root.socket.on('audiobooks_updated', this.audiobooksUpdated)
|
this.$server.socket.on('audiobooks_updated', this.audiobooksUpdated)
|
||||||
// this.$root.socket.on('audiobooks_added', this.audiobooksAdded)
|
this.$server.socket.on('audiobooks_added', this.audiobooksAdded)
|
||||||
// } else {
|
} else {
|
||||||
// console.error('Bookshelf - Socket not initialized')
|
console.error('Bookshelf - Socket not initialized')
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
removeListeners() {
|
removeListeners() {
|
||||||
var bookshelf = document.getElementById('bookshelf-wrapper')
|
var bookshelf = document.getElementById('bookshelf-wrapper')
|
||||||
|
@ -372,22 +456,25 @@ export default {
|
||||||
bookshelf.removeEventListener('scroll', this.scroll)
|
bookshelf.removeEventListener('scroll', this.scroll)
|
||||||
}
|
}
|
||||||
this.$eventBus.$off('library-changed', this.libraryChanged)
|
this.$eventBus.$off('library-changed', this.libraryChanged)
|
||||||
|
this.$eventBus.$off('downloads-loaded', this.downloadsLoaded)
|
||||||
this.$store.commit('user/removeSettingsListener', 'lazy-bookshelf')
|
this.$store.commit('user/removeSettingsListener', 'lazy-bookshelf')
|
||||||
|
|
||||||
// if (this.$root.socket) {
|
if (this.$server.socket) {
|
||||||
// this.$root.socket.off('audiobook_updated', this.audiobookUpdated)
|
this.$server.socket.off('audiobook_updated', this.audiobookUpdated)
|
||||||
// this.$root.socket.off('audiobook_added', this.audiobookAdded)
|
this.$server.socket.off('audiobook_added', this.audiobookAdded)
|
||||||
// this.$root.socket.off('audiobook_removed', this.audiobookRemoved)
|
this.$server.socket.off('audiobook_removed', this.audiobookRemoved)
|
||||||
// this.$root.socket.off('audiobooks_updated', this.audiobooksUpdated)
|
this.$server.socket.off('audiobooks_updated', this.audiobooksUpdated)
|
||||||
// this.$root.socket.off('audiobooks_added', this.audiobooksAdded)
|
this.$server.socket.off('audiobooks_added', this.audiobooksAdded)
|
||||||
// } else {
|
} else {
|
||||||
// console.error('Bookshelf - Socket not initialized')
|
console.error('Bookshelf - Socket not initialized')
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.$server.initialized) {
|
if (this.$server.initialized) {
|
||||||
this.init()
|
this.init()
|
||||||
|
} else {
|
||||||
|
this.initDownloads()
|
||||||
}
|
}
|
||||||
this.$server.on('initialized', this.socketInit)
|
this.$server.on('initialized', this.socketInit)
|
||||||
this.initListeners()
|
this.initListeners()
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="w-full relative">
|
|
||||||
<div class="bookshelfRow h-48 flex items-end justify-around px-3 max-w-full">
|
|
||||||
<template v-for="book in books">
|
|
||||||
<cards-book-card :key="book.id" :audiobook="book" :width="108" class="mx-2" />
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w-full h-4 z-40 bookshelfDivider"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
books: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
computed: {},
|
|
||||||
methods: {},
|
|
||||||
mounted() {}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -2,7 +2,6 @@
|
||||||
<div class="w-full relative">
|
<div class="w-full relative">
|
||||||
<div class="bookshelfRow flex items-end px-3 max-w-full overflow-x-auto" :style="{ height: shelfHeight + 'px' }">
|
<div class="bookshelfRow flex items-end px-3 max-w-full overflow-x-auto" :style="{ height: shelfHeight + 'px' }">
|
||||||
<template v-for="(entity, index) in entities">
|
<template v-for="(entity, index) in entities">
|
||||||
<!-- <cards-book-card v-if="type === 'books'" :key="entity.id" :audiobook="entity" :width="bookWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" class="mx-2" /> -->
|
|
||||||
<cards-lazy-book-card v-if="type === 'books'" :key="entity.id" :index="index" :book-mount="entity" :width="bookWidth" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" is-categorized class="mx-2 relative" />
|
<cards-lazy-book-card v-if="type === 'books'" :key="entity.id" :index="index" :book-mount="entity" :width="bookWidth" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" is-categorized class="mx-2 relative" />
|
||||||
<cards-lazy-series-card v-else-if="type === 'series'" :key="entity.id" :index="index" :series-mount="entity" :width="bookWidth * 2" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" is-categorized class="mx-2 relative" />
|
<cards-lazy-series-card v-else-if="type === 'series'" :key="entity.id" :index="index" :series-mount="entity" :width="bookWidth * 2" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" is-categorized class="mx-2 relative" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="relative">
|
|
||||||
<div class="rounded-sm h-full overflow-hidden relative box-shadow-book">
|
|
||||||
<nuxt-link :to="`/audiobook/${audiobookId}`" class="cursor-pointer">
|
|
||||||
<div class="w-full relative" :style="{ height: height + 'px' }">
|
|
||||||
<covers-book-cover :audiobook="audiobook" :download-cover="downloadCover" :width="width" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
|
||||||
|
|
||||||
<div v-if="download" class="absolute" :style="{ top: 0.5 * sizeMultiplier + 'rem', right: 0.5 * sizeMultiplier + 'rem' }">
|
|
||||||
<span class="material-icons text-success" :style="{ fontSize: 1.1 * sizeMultiplier + 'rem' }">download_done</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="absolute bottom-0 left-0 h-1.5 shadow-sm" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
|
||||||
|
|
||||||
<div v-if="showError" :style="{ height: 1.5 * sizeMultiplier + 'rem', width: 2.5 * sizeMultiplier + 'rem', bottom: sizeMultiplier + 'rem' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300 absolute left-0">
|
|
||||||
<span class="material-icons text-red-100 pr-1" :style="{ fontSize: 0.875 * sizeMultiplier + 'rem' }">priority_high</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nuxt-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
audiobook: {
|
|
||||||
type: Object,
|
|
||||||
default: () => null
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: Number,
|
|
||||||
default: 140
|
|
||||||
},
|
|
||||||
bookCoverAspectRatio: Number
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
tags() {
|
|
||||||
return this.audiobook.tags || []
|
|
||||||
},
|
|
||||||
audiobookId() {
|
|
||||||
return this.audiobook.id
|
|
||||||
},
|
|
||||||
book() {
|
|
||||||
return this.audiobook.book || {}
|
|
||||||
},
|
|
||||||
height() {
|
|
||||||
return this.width * this.bookCoverAspectRatio
|
|
||||||
},
|
|
||||||
sizeMultiplier() {
|
|
||||||
if (this.bookCoverAspectRatio === 1) return this.width / 160
|
|
||||||
return this.width / 100
|
|
||||||
},
|
|
||||||
mostRecentUserProgress() {
|
|
||||||
return this.$store.getters['user/getUserAudiobookData'](this.audiobookId)
|
|
||||||
},
|
|
||||||
userProgressPercent() {
|
|
||||||
return this.mostRecentUserProgress ? this.mostRecentUserProgress.progress || 0 : 0
|
|
||||||
},
|
|
||||||
userIsRead() {
|
|
||||||
return this.mostRecentUserProgress ? !!this.mostRecentUserProgress.isRead : false
|
|
||||||
},
|
|
||||||
showError() {
|
|
||||||
return this.hasMissingParts || this.hasInvalidParts
|
|
||||||
},
|
|
||||||
hasMissingParts() {
|
|
||||||
return this.audiobook.hasMissingParts
|
|
||||||
},
|
|
||||||
hasInvalidParts() {
|
|
||||||
return this.audiobook.hasInvalidParts
|
|
||||||
},
|
|
||||||
downloadCover() {
|
|
||||||
return this.download ? this.download.cover : null
|
|
||||||
},
|
|
||||||
download() {
|
|
||||||
return this.$store.getters['downloads/getDownloadIfReady'](this.audiobookId)
|
|
||||||
},
|
|
||||||
errorText() {
|
|
||||||
var txt = ''
|
|
||||||
if (this.hasMissingParts) {
|
|
||||||
txt = `${this.hasMissingParts} missing parts.`
|
|
||||||
}
|
|
||||||
if (this.hasInvalidParts) {
|
|
||||||
if (this.hasMissingParts) txt += ' '
|
|
||||||
txt += `${this.hasInvalidParts} invalid parts.`
|
|
||||||
}
|
|
||||||
return txt || 'Unknown Error'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {},
|
|
||||||
mounted() {}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,167 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="relative rounded-sm overflow-hidden" :style="{ height: width * 1.6 + 'px', width: width + 'px', maxWidth: width + 'px', minWidth: width + 'px' }">
|
|
||||||
<div class="w-full h-full relative">
|
|
||||||
<div class="bg-primary absolute top-0 left-0 w-full h-full">
|
|
||||||
<!-- Blurred background for covers that dont fill -->
|
|
||||||
<div v-if="showCoverBg" class="w-full h-full z-0" ref="coverBg" />
|
|
||||||
|
|
||||||
<!-- Image Loading indicator -->
|
|
||||||
<div v-if="!isImageLoaded" class="w-full h-full flex items-center justify-center text-white">
|
|
||||||
<svg class="animate-spin w-12 h-12" viewBox="0 0 24 24">
|
|
||||||
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<img ref="cover" :src="fullCoverUrl" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-cover'" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="imageFailed" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-red-100" :style="{ padding: placeholderCoverPadding + 'rem' }">
|
|
||||||
<div class="w-full h-full border-2 border-error flex flex-col items-center justify-center">
|
|
||||||
<img src="/Logo.png" class="mb-2" :style="{ height: 64 * sizeMultiplier + 'px' }" />
|
|
||||||
<p class="text-center font-book text-error" :style="{ fontSize: titleFontSize + 'rem' }">Invalid Cover</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem' }">
|
|
||||||
<div>
|
|
||||||
<p class="text-center font-book" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">{{ titleCleaned }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem', bottom: authorBottom + 'rem' }">
|
|
||||||
<p class="text-center font-book" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
audiobook: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
},
|
|
||||||
downloadCover: String,
|
|
||||||
authorOverride: String,
|
|
||||||
width: {
|
|
||||||
type: Number,
|
|
||||||
default: 120
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
imageFailed: false,
|
|
||||||
showCoverBg: false,
|
|
||||||
isImageLoaded: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
cover() {
|
|
||||||
this.imageFailed = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
userToken() {
|
|
||||||
return this.$store.getters['user/getToken']
|
|
||||||
},
|
|
||||||
book() {
|
|
||||||
return this.audiobook.book || {}
|
|
||||||
},
|
|
||||||
title() {
|
|
||||||
return this.book.title || 'No Title'
|
|
||||||
},
|
|
||||||
titleCleaned() {
|
|
||||||
if (this.title.length > 60) {
|
|
||||||
return this.title.slice(0, 57) + '...'
|
|
||||||
}
|
|
||||||
return this.title
|
|
||||||
},
|
|
||||||
author() {
|
|
||||||
if (this.authorOverride) return this.authorOverride
|
|
||||||
return this.book.author || 'Unknown'
|
|
||||||
},
|
|
||||||
authorCleaned() {
|
|
||||||
if (this.author.length > 30) {
|
|
||||||
return this.author.slice(0, 27) + '...'
|
|
||||||
}
|
|
||||||
return this.author
|
|
||||||
},
|
|
||||||
placeholderUrl() {
|
|
||||||
return '/book_placeholder.jpg'
|
|
||||||
},
|
|
||||||
serverUrl() {
|
|
||||||
return this.$store.state.serverUrl
|
|
||||||
},
|
|
||||||
networkConnected() {
|
|
||||||
return this.$store.state.networkConnected
|
|
||||||
},
|
|
||||||
fullCoverUrl() {
|
|
||||||
if (this.downloadCover) return this.downloadCover
|
|
||||||
else if (!this.networkConnected) return this.placeholderUrl
|
|
||||||
return this.$store.getters['audiobooks/getBookCoverSrc'](this.audiobook)
|
|
||||||
// if (this.cover.startsWith('http')) return this.cover
|
|
||||||
// var _clean = this.cover.replace(/\\/g, '/')
|
|
||||||
// if (_clean.startsWith('/local')) {
|
|
||||||
// var _cover = process.env.NODE_ENV !== 'production' && process.env.PROD !== '1' ? _clean.replace('/local', '') : _clean
|
|
||||||
// return `${this.$store.state.serverUrl}${_cover}?token=${this.userToken}&ts=${Date.now()}`
|
|
||||||
// } else if (_clean.startsWith('/metadata')) {
|
|
||||||
// return `${this.$store.state.serverUrl}${_clean}?token=${this.userToken}&ts=${Date.now()}`
|
|
||||||
// }
|
|
||||||
// return _clean
|
|
||||||
},
|
|
||||||
cover() {
|
|
||||||
return this.book.cover || this.placeholderUrl
|
|
||||||
},
|
|
||||||
hasCover() {
|
|
||||||
if (!this.networkConnected && !this.downloadCover) return false
|
|
||||||
return !!this.book.cover
|
|
||||||
},
|
|
||||||
sizeMultiplier() {
|
|
||||||
return this.width / 120
|
|
||||||
},
|
|
||||||
titleFontSize() {
|
|
||||||
return 0.75 * this.sizeMultiplier
|
|
||||||
},
|
|
||||||
authorFontSize() {
|
|
||||||
return 0.6 * this.sizeMultiplier
|
|
||||||
},
|
|
||||||
placeholderCoverPadding() {
|
|
||||||
return 0.8 * this.sizeMultiplier
|
|
||||||
},
|
|
||||||
authorBottom() {
|
|
||||||
return 0.75 * this.sizeMultiplier
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setCoverBg() {
|
|
||||||
if (this.$refs.coverBg) {
|
|
||||||
this.$refs.coverBg.style.backgroundImage = `url("${this.fullCoverUrl}")`
|
|
||||||
this.$refs.coverBg.style.backgroundSize = 'cover'
|
|
||||||
this.$refs.coverBg.style.backgroundPosition = 'center'
|
|
||||||
this.$refs.coverBg.style.opacity = 0.25
|
|
||||||
this.$refs.coverBg.style.filter = 'blur(1px)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hideCoverBg() {},
|
|
||||||
imageLoaded() {
|
|
||||||
if (this.$refs.cover && this.cover !== this.placeholderUrl) {
|
|
||||||
var { naturalWidth, naturalHeight } = this.$refs.cover
|
|
||||||
var aspectRatio = naturalHeight / naturalWidth
|
|
||||||
var arDiff = Math.abs(aspectRatio - 1.6)
|
|
||||||
|
|
||||||
// If image aspect ratio is <= 1.45 or >= 1.75 then use cover bg, otherwise stretch to fit
|
|
||||||
if (arDiff > 0.15) {
|
|
||||||
this.showCoverBg = true
|
|
||||||
this.$nextTick(this.setCoverBg)
|
|
||||||
} else {
|
|
||||||
this.showCoverBg = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.isImageLoaded = true
|
|
||||||
},
|
|
||||||
imageError(err) {
|
|
||||||
this.imageFailed = true
|
|
||||||
console.error('ImgError', err, `SET IMAGE FAILED ${this.imageFailed}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,86 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="relative">
|
|
||||||
<div class="rounded-sm h-full relative" @click="clickCard">
|
|
||||||
<nuxt-link :to="groupTo" class="cursor-pointer">
|
|
||||||
<div class="w-full relative bg-primary" :style="{ height: coverHeight + 'px', width: coverWidth + 'px' }">
|
|
||||||
<cards-collection-cover ref="groupcover" :book-items="bookItems" :width="coverWidth" :height="coverHeight" />
|
|
||||||
</div>
|
|
||||||
</nuxt-link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-5 h-5 rounded-md font-book text-center" :style="{ width: Math.min(160, coverWidth) + 'px' }">
|
|
||||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.8 * sizeMultiplier}rem` }">
|
|
||||||
<p class="truncate pt-px" :style="{ fontSize: labelFontSize + 'rem' }">{{ collectionName }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
collection: {
|
|
||||||
type: Object,
|
|
||||||
default: () => null
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: Number,
|
|
||||||
default: 120
|
|
||||||
},
|
|
||||||
paddingY: {
|
|
||||||
type: Number,
|
|
||||||
default: 24
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
width(newVal) {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (this.$refs.groupcover && this.$refs.groupcover.init) {
|
|
||||||
this.$refs.groupcover.init()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labelFontSize() {
|
|
||||||
if (this.coverWidth < 160) return 0.7
|
|
||||||
return 0.75
|
|
||||||
},
|
|
||||||
currentLibraryId() {
|
|
||||||
return this.$store.state.libraries.currentLibraryId
|
|
||||||
},
|
|
||||||
_collection() {
|
|
||||||
return this.collection || {}
|
|
||||||
},
|
|
||||||
groupTo() {
|
|
||||||
return `/collection/${this._collection.id}`
|
|
||||||
},
|
|
||||||
coverWidth() {
|
|
||||||
return this.width * 2
|
|
||||||
},
|
|
||||||
coverHeight() {
|
|
||||||
return this.width * 1.6
|
|
||||||
},
|
|
||||||
sizeMultiplier() {
|
|
||||||
return this.width / 120
|
|
||||||
},
|
|
||||||
paddingX() {
|
|
||||||
return 16 * this.sizeMultiplier
|
|
||||||
},
|
|
||||||
bookItems() {
|
|
||||||
return this._collection.books || []
|
|
||||||
},
|
|
||||||
collectionName() {
|
|
||||||
return this._collection.name || 'No Name'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
clickCard() {
|
|
||||||
this.$emit('click', this.collection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,63 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="relative rounded-sm overflow-hidden" :style="{ width: width + 'px', height: height + 'px' }">
|
|
||||||
<!-- <div class="absolute top-0 left-0 w-full h-full rounded-sm overflow-hidden z-10">
|
|
||||||
<div class="w-full h-full border border-white border-opacity-10" />
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<div v-if="hasOwnCover" class="w-full h-full relative rounded-sm">
|
|
||||||
<div v-if="showCoverBg" class="bg-primary absolute top-0 left-0 w-full h-full">
|
|
||||||
<div class="w-full h-full z-0" ref="coverBg" />
|
|
||||||
</div>
|
|
||||||
<img ref="cover" :src="fullCoverUrl" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-cover'" />
|
|
||||||
</div>
|
|
||||||
<div v-else-if="books.length" class="flex justify-center h-full relative bg-primary bg-opacity-95 rounded-sm">
|
|
||||||
<div class="absolute top-0 left-0 w-full h-full bg-gray-400 bg-opacity-5" />
|
|
||||||
|
|
||||||
<cards-book-cover :audiobook="books[0]" :width="width / 2" />
|
|
||||||
<cards-book-cover v-if="books.length > 1" :audiobook="books[1]" :width="width / 2" />
|
|
||||||
</div>
|
|
||||||
<div v-else class="relative w-full h-full flex items-center justify-center p-2 bg-primary rounded-sm">
|
|
||||||
<div class="absolute top-0 left-0 w-full h-full bg-gray-400 bg-opacity-5" />
|
|
||||||
|
|
||||||
<p class="font-book text-white text-opacity-60 text-center" :style="{ fontSize: Math.min(1, sizeMultiplier) + 'rem' }">Empty Collection</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
bookItems: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
width: Number,
|
|
||||||
height: Number
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
imageFailed: false,
|
|
||||||
showCoverBg: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
sizeMultiplier() {
|
|
||||||
return this.width / 120
|
|
||||||
},
|
|
||||||
hasOwnCover() {
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
fullCoverUrl() {
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
books() {
|
|
||||||
return this.bookItems || []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
imageError() {},
|
|
||||||
imageLoaded() {}
|
|
||||||
},
|
|
||||||
mounted() {}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -23,6 +23,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Downloaded indicator icon -->
|
||||||
|
<div v-if="hasDownload" class="absolute z-10" :style="{ top: 0.5 * sizeMultiplier + 'rem', right: 0.5 * sizeMultiplier + 'rem' }">
|
||||||
|
<span class="material-icons text-success" :style="{ fontSize: 1.1 * sizeMultiplier + 'rem' }">download_done</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress bar -->
|
||||||
<div class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
<div class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
||||||
|
|
||||||
<div v-if="showError" :style="{ height: 1.5 * sizeMultiplier + 'rem', width: 2.5 * sizeMultiplier + 'rem' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300">
|
<div v-if="showError" :style="{ height: 1.5 * sizeMultiplier + 'rem', width: 2.5 * sizeMultiplier + 'rem' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300">
|
||||||
|
@ -74,7 +80,15 @@ export default {
|
||||||
placeholderUrl() {
|
placeholderUrl() {
|
||||||
return '/book_placeholder.jpg'
|
return '/book_placeholder.jpg'
|
||||||
},
|
},
|
||||||
|
hasDownload() {
|
||||||
|
return !!this._audiobook.download
|
||||||
|
},
|
||||||
|
downloadedCover() {
|
||||||
|
if (!this._audiobook.download) return null
|
||||||
|
return this._audiobook.download.cover
|
||||||
|
},
|
||||||
bookCoverSrc() {
|
bookCoverSrc() {
|
||||||
|
if (this.downloadedCover) return this.downloadedCover
|
||||||
return this.store.getters['audiobooks/getBookCoverSrc'](this._audiobook, this.placeholderUrl)
|
return this.store.getters['audiobooks/getBookCoverSrc'](this._audiobook, this.placeholderUrl)
|
||||||
},
|
},
|
||||||
audiobookId() {
|
audiobookId() {
|
||||||
|
@ -96,7 +110,7 @@ export default {
|
||||||
return this.bookCoverAspectRatio === 1
|
return this.bookCoverAspectRatio === 1
|
||||||
},
|
},
|
||||||
sizeMultiplier() {
|
sizeMultiplier() {
|
||||||
var baseSize = this.squareAspectRatio ? 192 : 120
|
var baseSize = this.squareAspectRatio ? 160 : 100
|
||||||
return this.width / baseSize
|
return this.width / baseSize
|
||||||
},
|
},
|
||||||
title() {
|
title() {
|
||||||
|
|
|
@ -1,167 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="relative rounded-sm overflow-hidden w-full h-full">
|
|
||||||
<div class="w-full h-full relative">
|
|
||||||
<div class="bg-primary absolute top-0 left-0 w-full h-full">
|
|
||||||
<!-- Blurred background for covers that dont fill -->
|
|
||||||
<div v-if="showCoverBg" class="w-full h-full z-0" ref="coverBg" />
|
|
||||||
|
|
||||||
<!-- Image Loading indicator -->
|
|
||||||
<div v-if="!isImageLoaded" class="w-full h-full flex items-center justify-center text-white">
|
|
||||||
<svg class="animate-spin w-12 h-12" viewBox="0 0 24 24">
|
|
||||||
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<img ref="cover" :src="fullCoverUrl" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-cover'" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="imageFailed" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-red-100" :style="{ padding: placeholderCoverPadding + 'rem' }">
|
|
||||||
<div class="w-full h-full border-2 border-error flex flex-col items-center justify-center">
|
|
||||||
<img src="/Logo.png" class="mb-2" :style="{ height: 64 * sizeMultiplier + 'px' }" />
|
|
||||||
<p class="text-center font-book text-error" :style="{ fontSize: titleFontSize + 'rem' }">Invalid Cover</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem' }">
|
|
||||||
<div>
|
|
||||||
<p class="text-center font-book" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">{{ titleCleaned }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem', bottom: authorBottom + 'rem' }">
|
|
||||||
<p class="text-center font-book" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
audiobook: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
},
|
|
||||||
downloadCover: String,
|
|
||||||
authorOverride: String,
|
|
||||||
width: {
|
|
||||||
type: Number,
|
|
||||||
default: 120
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
imageFailed: false,
|
|
||||||
showCoverBg: false,
|
|
||||||
isImageLoaded: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
cover() {
|
|
||||||
this.imageFailed = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
userToken() {
|
|
||||||
return this.$store.getters['user/getToken']
|
|
||||||
},
|
|
||||||
book() {
|
|
||||||
return this.audiobook.book || {}
|
|
||||||
},
|
|
||||||
title() {
|
|
||||||
return this.book.title || 'No Title'
|
|
||||||
},
|
|
||||||
titleCleaned() {
|
|
||||||
if (this.title.length > 60) {
|
|
||||||
return this.title.slice(0, 57) + '...'
|
|
||||||
}
|
|
||||||
return this.title
|
|
||||||
},
|
|
||||||
author() {
|
|
||||||
if (this.authorOverride) return this.authorOverride
|
|
||||||
return this.book.author || 'Unknown'
|
|
||||||
},
|
|
||||||
authorCleaned() {
|
|
||||||
if (this.author.length > 30) {
|
|
||||||
return this.author.slice(0, 27) + '...'
|
|
||||||
}
|
|
||||||
return this.author
|
|
||||||
},
|
|
||||||
placeholderUrl() {
|
|
||||||
return '/book_placeholder.jpg'
|
|
||||||
},
|
|
||||||
serverUrl() {
|
|
||||||
return this.$store.state.serverUrl
|
|
||||||
},
|
|
||||||
networkConnected() {
|
|
||||||
return this.$store.state.networkConnected
|
|
||||||
},
|
|
||||||
fullCoverUrl() {
|
|
||||||
if (this.downloadCover) return this.downloadCover
|
|
||||||
else if (!this.networkConnected) return this.placeholderUrl
|
|
||||||
return this.$store.getters['audiobooks/getBookCoverSrc'](this.audiobook)
|
|
||||||
// if (this.cover.startsWith('http')) return this.cover
|
|
||||||
// var _clean = this.cover.replace(/\\/g, '/')
|
|
||||||
// if (_clean.startsWith('/local')) {
|
|
||||||
// var _cover = process.env.NODE_ENV !== 'production' && process.env.PROD !== '1' ? _clean.replace('/local', '') : _clean
|
|
||||||
// return `${this.$store.state.serverUrl}${_cover}?token=${this.userToken}&ts=${Date.now()}`
|
|
||||||
// } else if (_clean.startsWith('/metadata')) {
|
|
||||||
// return `${this.$store.state.serverUrl}${_clean}?token=${this.userToken}&ts=${Date.now()}`
|
|
||||||
// }
|
|
||||||
// return _clean
|
|
||||||
},
|
|
||||||
cover() {
|
|
||||||
return this.book.cover || this.placeholderUrl
|
|
||||||
},
|
|
||||||
hasCover() {
|
|
||||||
if (!this.networkConnected && !this.downloadCover) return false
|
|
||||||
return !!this.book.cover
|
|
||||||
},
|
|
||||||
sizeMultiplier() {
|
|
||||||
return this.width / 120
|
|
||||||
},
|
|
||||||
titleFontSize() {
|
|
||||||
return 0.75 * this.sizeMultiplier
|
|
||||||
},
|
|
||||||
authorFontSize() {
|
|
||||||
return 0.6 * this.sizeMultiplier
|
|
||||||
},
|
|
||||||
placeholderCoverPadding() {
|
|
||||||
return 0.8 * this.sizeMultiplier
|
|
||||||
},
|
|
||||||
authorBottom() {
|
|
||||||
return 0.75 * this.sizeMultiplier
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setCoverBg() {
|
|
||||||
if (this.$refs.coverBg) {
|
|
||||||
this.$refs.coverBg.style.backgroundImage = `url("${this.fullCoverUrl}")`
|
|
||||||
this.$refs.coverBg.style.backgroundSize = 'cover'
|
|
||||||
this.$refs.coverBg.style.backgroundPosition = 'center'
|
|
||||||
this.$refs.coverBg.style.opacity = 0.25
|
|
||||||
this.$refs.coverBg.style.filter = 'blur(1px)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hideCoverBg() {},
|
|
||||||
imageLoaded() {
|
|
||||||
if (this.$refs.cover && this.cover !== this.placeholderUrl) {
|
|
||||||
var { naturalWidth, naturalHeight } = this.$refs.cover
|
|
||||||
var aspectRatio = naturalHeight / naturalWidth
|
|
||||||
var arDiff = Math.abs(aspectRatio - 1.6)
|
|
||||||
|
|
||||||
// If image aspect ratio is <= 1.45 or >= 1.75 then use cover bg, otherwise stretch to fit
|
|
||||||
if (arDiff > 0.15) {
|
|
||||||
this.showCoverBg = true
|
|
||||||
this.$nextTick(this.setCoverBg)
|
|
||||||
} else {
|
|
||||||
this.showCoverBg = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.isImageLoaded = true
|
|
||||||
},
|
|
||||||
imageError(err) {
|
|
||||||
this.imageFailed = true
|
|
||||||
console.error('ImgError', err, `SET IMAGE FAILED ${this.imageFailed}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,113 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="rounded-sm relative" @click="clickCard">
|
|
||||||
<nuxt-link :to="groupTo" class="cursor-pointer">
|
|
||||||
<div class="w-full relative bg-primary" :style="{ height: coverHeight + 'px', width: coverWidth + 'px' }">
|
|
||||||
<cards-series-cover ref="groupcover" :name="groupName" :group-to="groupTo" :type="groupType" :book-items="bookItems" :width="coverWidth" :height="coverHeight" />
|
|
||||||
|
|
||||||
<div class="absolute top-2 right-2 w-7 h-7 rounded-lg bg-black bg-opacity-90 text-gray-300 box-shadow-book flex items-center justify-center border border-white border-opacity-25 pointer-events-none z-40">
|
|
||||||
<p class="font-book text-xl">{{ bookItems.length }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="absolute bottom-0 left-0 w-full h-1 flex flex-nowrap z-40">
|
|
||||||
<div v-for="userProgress in userProgressItems" :key="userProgress.audiobookId" class="h-full w-full" :class="userProgress.isRead ? 'bg-success' : userProgress.progress > 0 ? 'bg-yellow-400' : ''" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nuxt-link>
|
|
||||||
|
|
||||||
<div class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-5 h-5 rounded-md font-book text-center" :style="{ width: Math.min(160, coverWidth) + 'px' }">
|
|
||||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.8 * sizeMultiplier}rem` }">
|
|
||||||
<p class="truncate pt-px" :style="{ fontSize: labelFontSize + 'rem' }">{{ groupName }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
group: {
|
|
||||||
type: Object,
|
|
||||||
default: () => null
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: Number,
|
|
||||||
default: 120
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
width(newVal) {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (this.$refs.groupcover && this.$refs.groupcover.init) {
|
|
||||||
this.$refs.groupcover.init()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
currentLibraryId() {
|
|
||||||
return this.$store.state.libraries.currentLibraryId
|
|
||||||
},
|
|
||||||
labelFontSize() {
|
|
||||||
if (this.coverWidth < 160) return 0.7
|
|
||||||
return 0.75
|
|
||||||
},
|
|
||||||
_group() {
|
|
||||||
return this.group || {}
|
|
||||||
},
|
|
||||||
groupType() {
|
|
||||||
return this._group.type
|
|
||||||
},
|
|
||||||
groupTo() {
|
|
||||||
if (this.groupType === 'series') {
|
|
||||||
return `/bookshelf/series?series=${this.groupEncode}`
|
|
||||||
} else {
|
|
||||||
return `/bookshelf?filter=tags.${this.groupEncode}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
coverWidth() {
|
|
||||||
return this.coverHeight
|
|
||||||
},
|
|
||||||
coverHeight() {
|
|
||||||
return this.width * 1.6
|
|
||||||
},
|
|
||||||
sizeMultiplier() {
|
|
||||||
return this.width / 120
|
|
||||||
},
|
|
||||||
paddingX() {
|
|
||||||
return 16 * this.sizeMultiplier
|
|
||||||
},
|
|
||||||
bookItems() {
|
|
||||||
return this._group.books || []
|
|
||||||
},
|
|
||||||
userAudiobooks() {
|
|
||||||
return Object.values(this.$store.state.user.user ? this.$store.state.user.user.audiobooks || {} : {})
|
|
||||||
},
|
|
||||||
userProgressItems() {
|
|
||||||
return this.bookItems.map((item) => {
|
|
||||||
var userAudiobook = this.userAudiobooks.find((ab) => ab.audiobookId === item.id)
|
|
||||||
return userAudiobook || {}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
groupName() {
|
|
||||||
return this._group.name || 'No Name'
|
|
||||||
},
|
|
||||||
groupEncode() {
|
|
||||||
return this.$encode(this.groupName)
|
|
||||||
},
|
|
||||||
filter() {
|
|
||||||
return `${this.groupType}.${this.$encode(this.groupName)}`
|
|
||||||
},
|
|
||||||
hasValidCovers() {
|
|
||||||
var validCovers = this.bookItems.map((bookItem) => bookItem.book.cover)
|
|
||||||
return !!validCovers.length
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
clickCard() {
|
|
||||||
this.$emit('click', this.group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,171 +0,0 @@
|
||||||
<template>
|
|
||||||
<div ref="wrapper" :style="{ height: height + 'px', width: width + 'px' }" class="relative">
|
|
||||||
<div v-if="noValidCovers" class="absolute top-0 left-0 w-full h-full flex items-center justify-center box-shadow-book" :style="{ padding: `${sizeMultiplier}rem` }">
|
|
||||||
<p :style="{ fontSize: sizeMultiplier + 'rem' }">{{ name }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
name: String,
|
|
||||||
bookItems: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
width: Number,
|
|
||||||
height: Number
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
noValidCovers: false,
|
|
||||||
coverDiv: null,
|
|
||||||
coverWrapperEl: null,
|
|
||||||
coverImageEls: [],
|
|
||||||
coverWidth: 0,
|
|
||||||
offsetIncrement: 0,
|
|
||||||
windowWidth: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
bookItems: {
|
|
||||||
immediate: true,
|
|
||||||
handler(newVal) {
|
|
||||||
if (newVal) {
|
|
||||||
// ensure wrapper is initialized
|
|
||||||
this.$nextTick(this.init)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
sizeMultiplier() {
|
|
||||||
return this.width / 192
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getCoverUrl(book) {
|
|
||||||
return this.$store.getters['audiobooks/getBookCoverSrc'](book, '')
|
|
||||||
},
|
|
||||||
async buildCoverImg(coverData, bgCoverWidth, offsetLeft, zIndex, forceCoverBg = false) {
|
|
||||||
var src = coverData.coverUrl
|
|
||||||
|
|
||||||
var showCoverBg =
|
|
||||||
forceCoverBg ||
|
|
||||||
(await new Promise((resolve) => {
|
|
||||||
var image = new Image()
|
|
||||||
|
|
||||||
image.onload = () => {
|
|
||||||
var { naturalWidth, naturalHeight } = image
|
|
||||||
var aspectRatio = naturalHeight / naturalWidth
|
|
||||||
var arDiff = Math.abs(aspectRatio - 1.6)
|
|
||||||
|
|
||||||
// If image aspect ratio is <= 1.45 or >= 1.75 then use cover bg, otherwise stretch to fit
|
|
||||||
if (arDiff > 0.15) {
|
|
||||||
resolve(true)
|
|
||||||
} else {
|
|
||||||
resolve(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
image.onerror = (err) => {
|
|
||||||
console.error(err)
|
|
||||||
resolve(false)
|
|
||||||
}
|
|
||||||
image.src = src
|
|
||||||
}))
|
|
||||||
|
|
||||||
var imgdiv = document.createElement('div')
|
|
||||||
imgdiv.style.height = this.height + 'px'
|
|
||||||
imgdiv.style.width = bgCoverWidth + 'px'
|
|
||||||
imgdiv.style.left = offsetLeft + 'px'
|
|
||||||
imgdiv.style.zIndex = zIndex
|
|
||||||
imgdiv.dataset.audiobookId = coverData.id
|
|
||||||
imgdiv.dataset.volumeNumber = coverData.volumeNumber || ''
|
|
||||||
imgdiv.className = 'absolute top-0 box-shadow-book transition-transform'
|
|
||||||
imgdiv.style.boxShadow = '4px 0px 4px #11111166'
|
|
||||||
// imgdiv.style.transform = 'skew(0deg, 15deg)'
|
|
||||||
|
|
||||||
if (showCoverBg) {
|
|
||||||
var coverbgwrapper = document.createElement('div')
|
|
||||||
coverbgwrapper.className = 'absolute top-0 left-0 w-full h-full bg-primary'
|
|
||||||
|
|
||||||
var coverbg = document.createElement('div')
|
|
||||||
coverbg.className = 'w-full h-full'
|
|
||||||
coverbg.style.backgroundImage = `url("${src}")`
|
|
||||||
coverbg.style.backgroundSize = 'cover'
|
|
||||||
coverbg.style.backgroundPosition = 'center'
|
|
||||||
coverbg.style.opacity = 0.25
|
|
||||||
coverbg.style.filter = 'blur(1px)'
|
|
||||||
|
|
||||||
coverbgwrapper.appendChild(coverbg)
|
|
||||||
imgdiv.appendChild(coverbgwrapper)
|
|
||||||
}
|
|
||||||
|
|
||||||
var img = document.createElement('img')
|
|
||||||
img.src = src
|
|
||||||
img.className = 'absolute top-0 left-0 w-full h-full'
|
|
||||||
img.style.objectFit = showCoverBg ? 'contain' : 'cover'
|
|
||||||
|
|
||||||
imgdiv.appendChild(img)
|
|
||||||
return imgdiv
|
|
||||||
},
|
|
||||||
async init() {
|
|
||||||
if (this.coverDiv) {
|
|
||||||
this.coverDiv.remove()
|
|
||||||
this.coverDiv = null
|
|
||||||
}
|
|
||||||
var validCovers = this.bookItems
|
|
||||||
.map((bookItem) => {
|
|
||||||
return {
|
|
||||||
id: bookItem.id,
|
|
||||||
volumeNumber: bookItem.book ? bookItem.book.volumeNumber : null,
|
|
||||||
coverUrl: this.getCoverUrl(bookItem)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter((b) => b.coverUrl !== '')
|
|
||||||
if (!validCovers.length) {
|
|
||||||
this.noValidCovers = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.noValidCovers = false
|
|
||||||
|
|
||||||
var coverWidth = this.width
|
|
||||||
var widthPer = this.width
|
|
||||||
if (validCovers.length > 1) {
|
|
||||||
coverWidth = this.height / 1.6
|
|
||||||
widthPer = (this.width - coverWidth) / (validCovers.length - 1)
|
|
||||||
}
|
|
||||||
this.coverWidth = coverWidth
|
|
||||||
this.offsetIncrement = widthPer
|
|
||||||
|
|
||||||
var outerdiv = document.createElement('div')
|
|
||||||
this.coverWrapperEl = outerdiv
|
|
||||||
outerdiv.className = 'w-full h-full relative'
|
|
||||||
|
|
||||||
var coverImageEls = []
|
|
||||||
var offsetLeft = 0
|
|
||||||
for (let i = 0; i < validCovers.length; i++) {
|
|
||||||
offsetLeft = widthPer * i
|
|
||||||
var zIndex = validCovers.length - i
|
|
||||||
var img = await this.buildCoverImg(validCovers[i], coverWidth, offsetLeft, zIndex, validCovers.length === 1)
|
|
||||||
outerdiv.appendChild(img)
|
|
||||||
coverImageEls.push(img)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.coverImageEls = coverImageEls
|
|
||||||
|
|
||||||
if (this.$refs.wrapper) {
|
|
||||||
this.coverDiv = outerdiv
|
|
||||||
this.$refs.wrapper.appendChild(outerdiv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.windowWidth = window.innerWidth
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
if (this.coverWrapperEl) this.coverWrapperEl.remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -9,7 +9,7 @@
|
||||||
<p v-show="selectedSeriesName" class="ml-2 font-book pt-1">{{ selectedSeriesName }} ({{ totalEntities }})</p>
|
<p v-show="selectedSeriesName" class="ml-2 font-book pt-1">{{ selectedSeriesName }} ({{ totalEntities }})</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<template v-if="page === 'library'">
|
<template v-if="page === 'library'">
|
||||||
<span class="material-icons px-2" @click="changeView">{{ viewIcon }}</span>
|
<!-- <span class="material-icons px-2" @click="changeView">{{ viewIcon }}</span> -->
|
||||||
<div class="relative flex items-center px-2">
|
<div class="relative flex items-center px-2">
|
||||||
<span class="material-icons" @click="showFilterModal = true">filter_alt</span>
|
<span class="material-icons" @click="showFilterModal = true">filter_alt</span>
|
||||||
<div v-show="hasFilters" class="absolute top-0 right-2 w-2 h-2 rounded-full bg-success border border-green-300 shadow-sm z-10 pointer-events-none" />
|
<div v-show="hasFilters" class="absolute top-0 right-2 w-2 h-2 rounded-full bg-success border border-green-300 shadow-sm z-10 pointer-events-none" />
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
<template>
|
|
||||||
<modals-modal v-model="show" width="90%" height="100%">
|
|
||||||
<div class="w-full h-full overflow-hidden absolute top-0 left-0 flex items-center justify-center" @click="show = false">
|
|
||||||
<div class="w-full overflow-x-hidden overflow-y-auto bg-primary rounded-lg border border-white border-opacity-20 p-8" style="max-height: 75%" @click.stop>
|
|
||||||
<ui-text-input ref="input" v-model="search" @input="updateSearch" placeholder="Search" class="w-full text-lg" />
|
|
||||||
<div v-show="isFetching" class="w-full py-8 flex justify-center">
|
|
||||||
<p class="text-lg text-gray-400">Fetching...</p>
|
|
||||||
</div>
|
|
||||||
<div v-if="!isFetching && lastSearch && !items.length" class="w-full py-8 flex justify-center">
|
|
||||||
<p class="text-lg text-gray-400">Nothing found</p>
|
|
||||||
</div>
|
|
||||||
<template v-for="item in items">
|
|
||||||
<div class="py-2 border-b border-bg flex" :key="item.id" @click="clickItem(item)">
|
|
||||||
<cards-book-cover :audiobook="item.data" :width="50" />
|
|
||||||
<div class="flex-grow px-4 h-full">
|
|
||||||
<div class="w-full h-full">
|
|
||||||
<p class="text-base truncate">{{ item.data.book.title }}</p>
|
|
||||||
<p class="text-sm text-gray-400 truncate">{{ item.data.book.author }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</modals-modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
value: Boolean
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
search: null,
|
|
||||||
searchTimeout: null,
|
|
||||||
lastSearch: null,
|
|
||||||
isFetching: false,
|
|
||||||
items: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
value(newVal) {
|
|
||||||
if (newVal) {
|
|
||||||
this.$nextTick(this.setFocus())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
show: {
|
|
||||||
get() {
|
|
||||||
return this.value
|
|
||||||
},
|
|
||||||
set(val) {
|
|
||||||
this.$emit('input', val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
clickItem(item) {
|
|
||||||
this.show = false
|
|
||||||
this.$router.push(`/audiobook/${item.id}`)
|
|
||||||
},
|
|
||||||
async runSearch(value) {
|
|
||||||
this.lastSearch = value
|
|
||||||
if (!this.lastSearch) {
|
|
||||||
this.items = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.isFetching = true
|
|
||||||
var results = await this.$axios.$get(`/api/books?q=${value}`).catch((error) => {
|
|
||||||
console.error('Search error', error)
|
|
||||||
return []
|
|
||||||
})
|
|
||||||
this.isFetching = false
|
|
||||||
this.items = results.map((res) => {
|
|
||||||
return {
|
|
||||||
id: res.id,
|
|
||||||
data: res,
|
|
||||||
type: 'audiobook'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateSearch(val) {
|
|
||||||
clearTimeout(this.searchTimeout)
|
|
||||||
this.searchTimeout = setTimeout(() => {
|
|
||||||
this.runSearch(val)
|
|
||||||
}, 500)
|
|
||||||
},
|
|
||||||
setFocus() {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.$refs.input) {
|
|
||||||
this.$refs.input.focus()
|
|
||||||
}
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full px-2 py-2 overflow-hidden relative">
|
<div class="w-full px-2 py-2 overflow-hidden relative">
|
||||||
<div v-if="book" class="flex h-20">
|
<div v-if="book" class="flex h-20">
|
||||||
<div class="h-full relative" :style="{ width: '50px' }">
|
<div class="h-full relative" :style="{ width: bookWidth + 'px' }">
|
||||||
<cards-book-cover :audiobook="book" :width="50" />
|
<covers-book-cover :audiobook="book" :width="bookWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-80 h-full px-2 flex items-center">
|
<div class="w-80 h-full px-2 flex items-center">
|
||||||
<div>
|
<div>
|
||||||
|
@ -38,6 +38,13 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
bookCoverAspectRatio() {
|
||||||
|
return this.$store.getters['getBookCoverAspectRatio']
|
||||||
|
},
|
||||||
|
bookWidth() {
|
||||||
|
if (this.bookCoverAspectRatio === 1) return 80
|
||||||
|
return 50
|
||||||
|
},
|
||||||
_book() {
|
_book() {
|
||||||
return this.book.book || {}
|
return this.book.book || {}
|
||||||
},
|
},
|
||||||
|
|
|
@ -53,9 +53,13 @@ export default {
|
||||||
console.log('[Default] Connected socket sync user ab data')
|
console.log('[Default] Connected socket sync user ab data')
|
||||||
this.$store.dispatch('user/syncUserAudiobookData')
|
this.$store.dispatch('user/syncUserAudiobookData')
|
||||||
|
|
||||||
|
this.initSocketListeners()
|
||||||
|
|
||||||
// Load libraries
|
// Load libraries
|
||||||
this.$store.dispatch('libraries/load')
|
this.$store.dispatch('libraries/load')
|
||||||
this.$store.dispatch('libraries/fetch', this.currentLibraryId)
|
this.$store.dispatch('libraries/fetch', this.currentLibraryId)
|
||||||
|
} else {
|
||||||
|
this.removeSocketListeners()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
socketConnectionFailed(err) {
|
socketConnectionFailed(err) {
|
||||||
|
@ -260,18 +264,13 @@ export default {
|
||||||
this.onDownloadProgress(data)
|
this.onDownloadProgress(data)
|
||||||
})
|
})
|
||||||
|
|
||||||
// var downloads = (await this.$sqlStore.getAllDownloads()) || []
|
|
||||||
var downloads = await this.$store.dispatch('downloads/loadFromStorage')
|
var downloads = await this.$store.dispatch('downloads/loadFromStorage')
|
||||||
var downloadFolder = await this.$localStore.getDownloadFolder()
|
var downloadFolder = await this.$localStore.getDownloadFolder()
|
||||||
|
|
||||||
if (downloadFolder) {
|
if (downloadFolder) {
|
||||||
await this.syncDownloads(downloads, downloadFolder)
|
await this.syncDownloads(downloads, downloadFolder)
|
||||||
}
|
}
|
||||||
|
this.$eventBus.$emit('downloads-loaded')
|
||||||
var userSavedSettings = await this.$localStore.getUserSettings()
|
|
||||||
if (userSavedSettings) {
|
|
||||||
this.$store.commit('user/setSettings', userSavedSettings)
|
|
||||||
}
|
|
||||||
|
|
||||||
var checkPermission = await StorageManager.checkStoragePermission()
|
var checkPermission = await StorageManager.checkStoragePermission()
|
||||||
console.log('Storage Permission is' + checkPermission.value)
|
console.log('Storage Permission is' + checkPermission.value)
|
||||||
|
@ -282,6 +281,20 @@ export default {
|
||||||
this.$store.commit('setHasStoragePermission', true)
|
this.$store.commit('setHasStoragePermission', true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async loadSavedSettings() {
|
||||||
|
var userSavedServerSettings = await this.$localStore.getServerSettings()
|
||||||
|
if (userSavedServerSettings) {
|
||||||
|
this.$store.commit('setServerSettings', userSavedServerSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userSavedSettings = await this.$localStore.getUserSettings()
|
||||||
|
if (userSavedSettings) {
|
||||||
|
this.$store.commit('user/setSettings', userSavedSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Loading offline user audiobook data')
|
||||||
|
await this.$store.dispatch('user/loadOfflineUserAudiobookData')
|
||||||
|
},
|
||||||
showErrorToast(message) {
|
showErrorToast(message) {
|
||||||
this.$toast.error(message)
|
this.$toast.error(message)
|
||||||
},
|
},
|
||||||
|
@ -311,6 +324,53 @@ 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) {
|
||||||
|
this.$router.replace(`/bookshelf`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
@ -321,6 +381,7 @@ export default {
|
||||||
console.log('Syncing on default mount')
|
console.log('Syncing on default mount')
|
||||||
this.$store.dispatch('user/syncUserAudiobookData')
|
this.$store.dispatch('user/syncUserAudiobookData')
|
||||||
}
|
}
|
||||||
|
this.$server.on('logout', this.userLoggedOut)
|
||||||
this.$server.on('connected', this.connected)
|
this.$server.on('connected', this.connected)
|
||||||
this.$server.on('connectionFailed', this.socketConnectionFailed)
|
this.$server.on('connectionFailed', this.socketConnectionFailed)
|
||||||
this.$server.on('initialStream', this.initialStream)
|
this.$server.on('initialStream', this.initialStream)
|
||||||
|
@ -333,6 +394,7 @@ export default {
|
||||||
await this.$store.dispatch('setupNetworkListener')
|
await this.$store.dispatch('setupNetworkListener')
|
||||||
this.attemptConnection()
|
this.attemptConnection()
|
||||||
this.checkForUpdate()
|
this.checkForUpdate()
|
||||||
|
this.loadSavedSettings()
|
||||||
this.initMediaStore()
|
this.initMediaStore()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -341,7 +403,8 @@ export default {
|
||||||
console.error('No Server beforeDestroy')
|
console.error('No Server beforeDestroy')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
this.removeSocketListeners()
|
||||||
|
this.$server.off('logout', this.userLoggedOut)
|
||||||
this.$server.off('connected', this.connected)
|
this.$server.off('connected', this.connected)
|
||||||
this.$server.off('connectionFailed', this.socketConnectionFailed)
|
this.$server.off('connectionFailed', this.socketConnectionFailed)
|
||||||
this.$server.off('initialStream', this.initialStream)
|
this.$server.off('initialStream', this.initialStream)
|
||||||
|
|
|
@ -21,7 +21,7 @@ export default {
|
||||||
var shelf = Math.floor(index / this.entitiesPerShelf)
|
var shelf = Math.floor(index / this.entitiesPerShelf)
|
||||||
var shelfEl = document.getElementById(`shelf-${shelf}`)
|
var shelfEl = document.getElementById(`shelf-${shelf}`)
|
||||||
if (!shelfEl) {
|
if (!shelfEl) {
|
||||||
console.error('invalid shelf', shelf, 'book index', index)
|
console.error('mount entity card invalid shelf', shelf, 'book index', index)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.entityIndexesMounted.push(index)
|
this.entityIndexesMounted.push(index)
|
||||||
|
@ -43,7 +43,9 @@ export default {
|
||||||
}
|
}
|
||||||
var shelfOffsetY = this.isBookEntity ? 24 : 16
|
var shelfOffsetY = this.isBookEntity ? 24 : 16
|
||||||
var row = index % this.entitiesPerShelf
|
var row = index % this.entitiesPerShelf
|
||||||
var shelfOffsetX = row * this.totalEntityCardWidth + this.bookshelfMarginLeft
|
|
||||||
|
var marginShiftLeft = 12
|
||||||
|
var shelfOffsetX = row * this.totalEntityCardWidth + this.bookshelfMarginLeft + marginShiftLeft
|
||||||
|
|
||||||
var ComponentClass = this.getComponentClass()
|
var ComponentClass = this.getComponentClass()
|
||||||
var props = {
|
var props = {
|
||||||
|
@ -67,10 +69,10 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.entityComponentRefs[index] = instance
|
this.entityComponentRefs[index] = instance
|
||||||
|
|
||||||
instance.$mount()
|
instance.$mount()
|
||||||
instance.$el.style.transform = `translate3d(${shelfOffsetX}px, ${shelfOffsetY}px, 0px)`
|
instance.$el.style.transform = `translate3d(${shelfOffsetX}px, ${shelfOffsetY}px, 0px)`
|
||||||
instance.$el.classList.add('absolute', 'top-0', 'left-0', 'mx-3')
|
|
||||||
|
instance.$el.classList.add('absolute', 'top-0', 'left-0')
|
||||||
shelfEl.appendChild(instance.$el)
|
shelfEl.appendChild(instance.$el)
|
||||||
|
|
||||||
if (this.entities[index]) {
|
if (this.entities[index]) {
|
||||||
|
|
|
@ -80,9 +80,6 @@ export default {
|
||||||
})
|
})
|
||||||
this.$server.logout()
|
this.$server.logout()
|
||||||
this.$router.push('/connect')
|
this.$router.push('/connect')
|
||||||
|
|
||||||
this.$store.commit('audiobooks/reset')
|
|
||||||
this.$store.dispatch('audiobooks/useDownloaded')
|
|
||||||
},
|
},
|
||||||
openAppStore() {
|
openAppStore() {
|
||||||
AppUpdate.openAppStore()
|
AppUpdate.openAppStore()
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="w-32">
|
<div class="w-32">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<cards-book-cover :audiobook="audiobook" :download-cover="downloadedCover" :width="128" />
|
<covers-book-cover :audiobook="audiobook" :download-cover="downloadedCover" :width="128" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||||
<div class="absolute bottom-0 left-0 h-1.5 bg-yellow-400 shadow-sm" :style="{ width: 128 * progressPercent + 'px' }"></div>
|
<div class="absolute bottom-0 left-0 h-1.5 bg-yellow-400 shadow-sm z-10" :style="{ width: 128 * progressPercent + 'px' }"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex my-4">
|
<div class="flex my-4">
|
||||||
<p v-if="numTracks" class="text-sm">{{ numTracks }} Tracks</p>
|
<p v-if="numTracks" class="text-sm">{{ numTracks }} Tracks</p>
|
||||||
|
@ -64,7 +64,10 @@ export default {
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
audiobook = store.getters['audiobooks/getAudiobook'](audiobookId)
|
var download = store.getters['downloads/getDownload'](audiobookId)
|
||||||
|
if (download) {
|
||||||
|
audiobook = download.audiobook
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!audiobook) {
|
if (!audiobook) {
|
||||||
|
@ -84,6 +87,9 @@ export default {
|
||||||
isConnected() {
|
isConnected() {
|
||||||
return this.$store.state.socketConnected
|
return this.$store.state.socketConnected
|
||||||
},
|
},
|
||||||
|
bookCoverAspectRatio() {
|
||||||
|
return this.$store.getters['getBookCoverAspectRatio']
|
||||||
|
},
|
||||||
audiobookId() {
|
audiobookId() {
|
||||||
return this.audiobook.id
|
return this.audiobook.id
|
||||||
},
|
},
|
||||||
|
@ -116,34 +122,20 @@ export default {
|
||||||
size() {
|
size() {
|
||||||
return this.audiobook.size
|
return this.audiobook.size
|
||||||
},
|
},
|
||||||
userAudiobooks() {
|
|
||||||
return this.$store.state.user.user ? this.$store.state.user.user.audiobooks || {} : {}
|
|
||||||
},
|
|
||||||
userAudiobook() {
|
userAudiobook() {
|
||||||
return this.userAudiobooks[this.audiobookId] || null
|
return this.$store.getters['user/getUserAudiobook'](this.audiobookId)
|
||||||
},
|
},
|
||||||
userToken() {
|
userToken() {
|
||||||
return this.$store.getters['user/getToken']
|
return this.$store.getters['user/getToken']
|
||||||
},
|
},
|
||||||
localUserAudiobooks() {
|
|
||||||
return this.$store.state.user.localUserAudiobooks || {}
|
|
||||||
},
|
|
||||||
localUserAudiobook() {
|
|
||||||
return this.localUserAudiobooks[this.audiobookId] || null
|
|
||||||
},
|
|
||||||
mostRecentUserAudiobook() {
|
|
||||||
if (!this.localUserAudiobook) return this.userAudiobook
|
|
||||||
if (!this.userAudiobook) return this.localUserAudiobook
|
|
||||||
return this.localUserAudiobook.lastUpdate > this.userAudiobook.lastUpdate ? this.localUserAudiobook : this.userAudiobook
|
|
||||||
},
|
|
||||||
userCurrentTime() {
|
userCurrentTime() {
|
||||||
return this.mostRecentUserAudiobook ? this.mostRecentUserAudiobook.currentTime : 0
|
return this.userAudiobook ? this.userAudiobook.currentTime : 0
|
||||||
},
|
},
|
||||||
userTimeRemaining() {
|
userTimeRemaining() {
|
||||||
return this.duration - this.userCurrentTime
|
return this.duration - this.userCurrentTime
|
||||||
},
|
},
|
||||||
progressPercent() {
|
progressPercent() {
|
||||||
return this.mostRecentUserAudiobook ? this.mostRecentUserAudiobook.progress : 0
|
return this.userAudiobook ? this.userAudiobook.progress : 0
|
||||||
},
|
},
|
||||||
isStreaming() {
|
isStreaming() {
|
||||||
return this.$store.getters['isAudiobookStreaming'](this.audiobookId)
|
return this.$store.getters['isAudiobookStreaming'](this.audiobookId)
|
||||||
|
@ -242,7 +234,8 @@ export default {
|
||||||
this.resettingProgress = false
|
this.resettingProgress = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
audiobookUpdated() {
|
audiobookUpdated(audiobook) {
|
||||||
|
if (audiobook.id === this.audiobookId) {
|
||||||
console.log('Audiobook Updated - Fetch full audiobook')
|
console.log('Audiobook Updated - Fetch full audiobook')
|
||||||
this.$axios
|
this.$axios
|
||||||
.$get(`/api/books/${this.audiobookId}`)
|
.$get(`/api/books/${this.audiobookId}`)
|
||||||
|
@ -252,6 +245,7 @@ export default {
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
downloadClick() {
|
downloadClick() {
|
||||||
console.log('downloadClick ' + this.$server.connected + ' | ' + !!this.downloadObj)
|
console.log('downloadClick ' + this.$server.connected + ' | ' + !!this.downloadObj)
|
||||||
|
@ -427,9 +421,8 @@ export default {
|
||||||
this.$server.socket.on('download_ready', this.downloadReady)
|
this.$server.socket.on('download_ready', this.downloadReady)
|
||||||
this.$server.socket.on('download_killed', this.downloadKilled)
|
this.$server.socket.on('download_killed', this.downloadKilled)
|
||||||
this.$server.socket.on('download_failed', this.downloadFailed)
|
this.$server.socket.on('download_failed', this.downloadFailed)
|
||||||
|
this.$server.socket.on('audiobook_updated', this.audiobookUpdated)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.commit('audiobooks/addListener', { id: 'audiobook', audiobookId: this.audiobookId, meth: this.audiobookUpdated })
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (!this.$server.socket) {
|
if (!this.$server.socket) {
|
||||||
|
@ -438,9 +431,8 @@ export default {
|
||||||
this.$server.socket.off('download_ready', this.downloadReady)
|
this.$server.socket.off('download_ready', this.downloadReady)
|
||||||
this.$server.socket.off('download_killed', this.downloadKilled)
|
this.$server.socket.off('download_killed', this.downloadKilled)
|
||||||
this.$server.socket.off('download_failed', this.downloadFailed)
|
this.$server.socket.off('download_failed', this.downloadFailed)
|
||||||
|
this.$server.socket.off('audiobook_updated', this.audiobookUpdated)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.commit('audiobooks/removeListener', 'audiobook')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -20,38 +20,7 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
isHome() {
|
isHome() {
|
||||||
return this.$route.name === 'bookshelf'
|
return this.$route.name === 'bookshelf'
|
||||||
},
|
|
||||||
currentLibrary() {
|
|
||||||
return this.$store.getters['libraries/getCurrentLibrary']
|
|
||||||
},
|
|
||||||
currentLibraryName() {
|
|
||||||
return this.currentLibrary ? this.currentLibrary.name : 'Main'
|
|
||||||
},
|
|
||||||
isSocketConnected() {
|
|
||||||
return this.$store.state.socketConnected
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async loadCollections() {
|
|
||||||
this.$store.dispatch('user/loadUserCollections')
|
|
||||||
},
|
|
||||||
socketConnected(isConnected) {
|
|
||||||
// if (isConnected) {
|
|
||||||
// console.log('Connected - Load from server')
|
|
||||||
// this.loadAudiobooks()
|
|
||||||
// if (this.$route.name === 'bookshelf-collections') this.loadCollections()
|
|
||||||
// } else {
|
|
||||||
// console.log('Disconnected - Reset to local storage')
|
|
||||||
// this.$store.commit('audiobooks/reset')
|
|
||||||
// this.$store.dispatch('audiobooks/useDownloaded')
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.$server.on('connected', this.socketConnected)
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.$server.off('connected', this.socketConnected)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,52 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<bookshelf-lazy-bookshelf page="collections" />
|
<bookshelf-lazy-bookshelf page="collections" />
|
||||||
<!-- <div class="w-full h-full">
|
|
||||||
<template v-for="(shelf, index) in shelves">
|
|
||||||
<bookshelf-group-shelf :key="shelf.id" group-type="collection" :groups="shelf.groups" :style="{ zIndex: shelves.length - index }" />
|
|
||||||
</template>
|
|
||||||
</div> -->
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {}
|
||||||
groupsPerRow: 2
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
watch: {},
|
watch: {},
|
||||||
computed: {
|
computed: {},
|
||||||
collections() {
|
methods: {}
|
||||||
return this.$store.state.user.collections || []
|
|
||||||
},
|
|
||||||
shelves() {
|
|
||||||
var shelves = []
|
|
||||||
var shelf = {
|
|
||||||
id: 0,
|
|
||||||
groups: []
|
|
||||||
}
|
|
||||||
for (let i = 0; i < this.collections.length; i++) {
|
|
||||||
var shelfNum = Math.floor((i + 1) / this.groupsPerRow)
|
|
||||||
shelf.id = shelfNum
|
|
||||||
shelf.groups.push(this.collections[i])
|
|
||||||
|
|
||||||
if ((i + 1) % this.groupsPerRow === 0) {
|
|
||||||
shelves.push(shelf)
|
|
||||||
shelf = {
|
|
||||||
id: 0,
|
|
||||||
groups: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (shelf.groups.length) {
|
|
||||||
shelves.push(shelf)
|
|
||||||
}
|
|
||||||
return shelves
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {},
|
|
||||||
mounted() {
|
|
||||||
this.$store.dispatch('user/loadUserCollections')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -36,7 +36,13 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
books() {
|
books() {
|
||||||
return this.$store.getters['downloads/getAudiobooks']
|
return this.$store.getters['downloads/getDownloads'].map((dl) => {
|
||||||
|
var download = { ...dl }
|
||||||
|
var ab = { ...download.audiobook }
|
||||||
|
delete download.audiobook
|
||||||
|
ab.download = download
|
||||||
|
return ab
|
||||||
|
})
|
||||||
},
|
},
|
||||||
isSocketConnected() {
|
isSocketConnected() {
|
||||||
return this.$store.state.socketConnected
|
return this.$store.state.socketConnected
|
||||||
|
@ -141,18 +147,108 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
this.shelves = this.downloadOnlyShelves
|
this.shelves = this.downloadOnlyShelves
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
downloadsLoaded() {
|
||||||
|
if (!this.isSocketConnected) {
|
||||||
|
this.shelves = this.downloadOnlyShelves
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
audiobookAdded(audiobook) {
|
||||||
this.$server.on('initialized', this.socketInit)
|
console.log('Audiobook added', audiobook)
|
||||||
this.$eventBus.$on('library-changed', this.libraryChanged)
|
// TODO: Check if audiobook would be on this shelf
|
||||||
if (this.$server.initialized) {
|
if (!this.search) {
|
||||||
this.fetchCategories()
|
this.fetchCategories()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
audiobookUpdated(audiobook) {
|
||||||
|
console.log('Audiobook updated', audiobook)
|
||||||
|
this.shelves.forEach((shelf) => {
|
||||||
|
if (shelf.type === 'books') {
|
||||||
|
shelf.entities = shelf.entities.map((ent) => {
|
||||||
|
if (ent.id === audiobook.id) {
|
||||||
|
return audiobook
|
||||||
|
}
|
||||||
|
return ent
|
||||||
|
})
|
||||||
|
} else if (shelf.type === 'series') {
|
||||||
|
shelf.entities.forEach((ent) => {
|
||||||
|
ent.books = ent.books.map((book) => {
|
||||||
|
if (book.id === audiobook.id) return audiobook
|
||||||
|
return book
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
removeBookFromShelf(audiobook) {
|
||||||
|
this.shelves.forEach((shelf) => {
|
||||||
|
if (shelf.type === 'books') {
|
||||||
|
shelf.entities = shelf.entities.filter((ent) => {
|
||||||
|
return ent.id !== audiobook.id
|
||||||
|
})
|
||||||
|
} else if (shelf.type === 'series') {
|
||||||
|
shelf.entities.forEach((ent) => {
|
||||||
|
ent.books = ent.books.filter((book) => {
|
||||||
|
return book.id !== audiobook.id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
audiobookRemoved(audiobook) {
|
||||||
|
this.removeBookFromShelf(audiobook)
|
||||||
|
},
|
||||||
|
audiobooksAdded(audiobooks) {
|
||||||
|
console.log('audiobooks added', audiobooks)
|
||||||
|
// TODO: Check if audiobook would be on this shelf
|
||||||
|
this.fetchCategories()
|
||||||
|
},
|
||||||
|
audiobooksUpdated(audiobooks) {
|
||||||
|
audiobooks.forEach((ab) => {
|
||||||
|
this.audiobookUpdated(ab)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
initListeners() {
|
||||||
|
this.$server.on('initialized', this.socketInit)
|
||||||
|
this.$eventBus.$on('library-changed', this.libraryChanged)
|
||||||
|
this.$eventBus.$on('downloads-loaded', this.downloadsLoaded)
|
||||||
|
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
console.error('Error socket not initialized')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeListeners() {
|
||||||
this.$server.off('initialized', this.socketInit)
|
this.$server.off('initialized', this.socketInit)
|
||||||
this.$eventBus.$off('library-changed', this.libraryChanged)
|
this.$eventBus.$off('library-changed', this.libraryChanged)
|
||||||
|
this.$eventBus.$off('downloads-loaded', this.downloadsLoaded)
|
||||||
|
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
console.error('Error socket not initialized')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initListeners()
|
||||||
|
if (this.$server.initialized) {
|
||||||
|
this.fetchCategories()
|
||||||
|
} else {
|
||||||
|
this.shelves = this.downloadOnlyShelves
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.removeListeners()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -1,11 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<bookshelf-lazy-bookshelf page="series" />
|
<bookshelf-lazy-bookshelf page="series" />
|
||||||
<!-- <div class="w-full h-full">
|
|
||||||
<template v-for="(shelf, index) in shelves">
|
|
||||||
<bookshelf-group-shelf v-if="!selectedSeriesName" :key="shelf.id" group-type="series" :groups="shelf.groups" :style="{ zIndex: shelves.length - index }" />
|
|
||||||
<bookshelf-library-shelf v-else :key="shelf.id" :books="shelf.books" :style="{ zIndex: shelves.length - index }" />
|
|
||||||
</template>
|
|
||||||
</div> -->
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="w-full h-full overflow-y-auto px-2 py-6 md:p-8">
|
<div class="w-full h-full overflow-y-auto px-2 py-6 md:p-8">
|
||||||
<div class="w-full flex justify-center md:block sm:w-32 md:w-52" style="min-width: 240px">
|
<div class="w-full flex justify-center md:block sm:w-32 md:w-52" style="min-width: 240px">
|
||||||
<div class="relative" style="height: fit-content">
|
<div class="relative" style="height: fit-content">
|
||||||
<cards-collection-cover :book-items="bookItems" :width="240" :height="120 * 1.6" />
|
<covers-collection-cover :book-items="bookItems" :width="240" :height="120 * bookCoverAspectRatio" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-2 py-6 md:py-0 md:px-10">
|
<div class="flex-grow px-2 py-6 md:py-0 md:px-10">
|
||||||
|
@ -48,15 +48,11 @@ export default {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
return redirect('/')
|
return redirect('/bookshelf')
|
||||||
}
|
}
|
||||||
|
|
||||||
store.commit('user/addUpdateCollection', collection)
|
|
||||||
collection.books.forEach((book) => {
|
|
||||||
store.commit('audiobooks/addUpdate', book)
|
|
||||||
})
|
|
||||||
return {
|
return {
|
||||||
collectionId: collection.id
|
collection
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -65,6 +61,9 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
bookCoverAspectRatio() {
|
||||||
|
return this.$store.getters['getBookCoverAspectRatio']
|
||||||
|
},
|
||||||
bookItems() {
|
bookItems() {
|
||||||
return this.collection.books || []
|
return this.collection.books || []
|
||||||
},
|
},
|
||||||
|
@ -74,9 +73,6 @@ export default {
|
||||||
description() {
|
description() {
|
||||||
return this.collection.description || ''
|
return this.collection.description || ''
|
||||||
},
|
},
|
||||||
collection() {
|
|
||||||
return this.$store.getters['user/getCollection'](this.collectionId)
|
|
||||||
},
|
|
||||||
playableBooks() {
|
playableBooks() {
|
||||||
return this.bookItems.filter((book) => {
|
return this.bookItems.filter((book) => {
|
||||||
return !book.isMissing && !book.isIncomplete && book.numTracks
|
return !book.isMissing && !book.isIncomplete && book.numTracks
|
||||||
|
|
|
@ -161,8 +161,6 @@ export default {
|
||||||
|
|
||||||
await this.searchFolder()
|
await this.searchFolder()
|
||||||
|
|
||||||
// var audiobooks = this.$store.state.audiobooks.audiobooks || []
|
|
||||||
// if (audiobooks.length) {
|
|
||||||
if (this.isSocketConnected) {
|
if (this.isSocketConnected) {
|
||||||
this.$store.dispatch('downloads/linkOrphanDownloads')
|
this.$store.dispatch('downloads/linkOrphanDownloads')
|
||||||
}
|
}
|
||||||
|
@ -240,7 +238,6 @@ export default {
|
||||||
AudioDownloader.addListener('onDownloadProgress', this.onDownloadProgress)
|
AudioDownloader.addListener('onDownloadProgress', this.onDownloadProgress)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {},
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
AudioDownloader.removeListener('onDownloadProgress', this.onDownloadProgress)
|
AudioDownloader.removeListener('onDownloadProgress', this.onDownloadProgress)
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,25 @@ class LocalStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setServerSettings(settings) {
|
||||||
|
try {
|
||||||
|
await Storage.set({ key: 'serverSettings', value: JSON.stringify(settings) })
|
||||||
|
console.log('Saved server settings', JSON.stringify(settings))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[LocalStorage] Failed to update server settings', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getServerSettings() {
|
||||||
|
try {
|
||||||
|
var settingsObj = await Storage.get({ key: 'serverSettings' }) || {}
|
||||||
|
return settingsObj.value ? JSON.parse(settingsObj.value) : null
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[LocalStorage] Failed to get server settings', error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async setCurrent(current) {
|
async setCurrent(current) {
|
||||||
try {
|
try {
|
||||||
if (current) {
|
if (current) {
|
||||||
|
|
|
@ -9,6 +9,11 @@ class StoreService {
|
||||||
constructor(vuexStore) {
|
constructor(vuexStore) {
|
||||||
this.vuexStore = vuexStore
|
this.vuexStore = vuexStore
|
||||||
this.currentTable = null
|
this.currentTable = null
|
||||||
|
|
||||||
|
this.lockWaitQueue = []
|
||||||
|
this.isLocked = false
|
||||||
|
this.lockedFor = null
|
||||||
|
|
||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,6 +275,53 @@ class StoreService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLockId(prefix) {
|
||||||
|
return prefix + '-' + Math.floor(Math.random() * 100000000).toString(32)
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForLock(id, count = 0) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.lockWaitQueue.includes(id)) {
|
||||||
|
resolve(true)
|
||||||
|
} else {
|
||||||
|
if (count > 200) {
|
||||||
|
console.error('[SqlStore] Lock was never released', id)
|
||||||
|
resolve(false)
|
||||||
|
} else {
|
||||||
|
resolve(this.waitForLock(id, ++count))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 50)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setLock(prefix) {
|
||||||
|
this.lockedFor = prefix
|
||||||
|
this.isLocked = true
|
||||||
|
console.log('[SqlStore] Locked for', this.lockedFor)
|
||||||
|
}
|
||||||
|
|
||||||
|
initWaitLock(prefix) {
|
||||||
|
var lockId = this.getLockId(prefix)
|
||||||
|
this.lockWaitQueue.push(lockId)
|
||||||
|
console.log('[SqlStore] Waiting for lock', lockId, 'In queue', this.lockWaitQueue.length)
|
||||||
|
return this.waitForLock(lockId)
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseLock() {
|
||||||
|
console.log('[SqlStore] Releasing lock', this.lockedFor)
|
||||||
|
if (!this.lockWaitQueue.length) {
|
||||||
|
console.log('[SqlStore] Release Lock no queue')
|
||||||
|
this.isLocked = false
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('[SqlStore] Release Lock Queue:', this.lockWaitQueue.length)
|
||||||
|
var task = this.lockWaitQueue.shift()
|
||||||
|
console.log('[SqlStore] Released lock next task', task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async ensureTable(tablename) {
|
async ensureTable(tablename) {
|
||||||
if (!this.isOpen) {
|
if (!this.isOpen) {
|
||||||
var success = await this.openStore('storage', tablename)
|
var success = await this.openStore('storage', tablename)
|
||||||
|
@ -290,42 +342,70 @@ class StoreService {
|
||||||
|
|
||||||
async setDownload(download) {
|
async setDownload(download) {
|
||||||
if (!download) return false
|
if (!download) return false
|
||||||
|
|
||||||
|
if (this.isLocked) {
|
||||||
|
await this.initWaitLock('setdl')
|
||||||
|
} else {
|
||||||
|
this.setLock('setdl')
|
||||||
|
}
|
||||||
|
|
||||||
if (!(await this.ensureTable('downloads'))) {
|
if (!(await this.ensureTable('downloads'))) {
|
||||||
|
this.releaseLock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (!download.id) {
|
if (!download.id) {
|
||||||
console.error(`[SqlStore] set download invalid download ${download ? JSON.stringify(download) : 'null'}`)
|
console.error(`[SqlStore] set download invalid download ${download ? JSON.stringify(download) : 'null'}`)
|
||||||
|
this.releaseLock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var success = false
|
||||||
try {
|
try {
|
||||||
await this.setItem(download.id, JSON.stringify(download))
|
await this.setItem(download.id, JSON.stringify(download))
|
||||||
console.log(`[STORE] Set Download ${download.id}`)
|
console.log(`[STORE] Set Download ${download.id}`)
|
||||||
return true
|
success = true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to set download in store', error)
|
console.error('Failed to set download in store', error)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
this.releaseLock()
|
||||||
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeDownload(id) {
|
async removeDownload(id) {
|
||||||
if (!id) return false
|
if (!id) return false
|
||||||
|
|
||||||
|
if (this.isLocked) {
|
||||||
|
await this.initWaitLock('remdl')
|
||||||
|
} else {
|
||||||
|
this.setLock('remdl')
|
||||||
|
}
|
||||||
|
|
||||||
if (!(await this.ensureTable('downloads'))) {
|
if (!(await this.ensureTable('downloads'))) {
|
||||||
|
this.releaseLock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var success = false
|
||||||
try {
|
try {
|
||||||
await this.removeItem(id)
|
await this.removeItem(id)
|
||||||
console.log(`[STORE] Removed download ${id}`)
|
console.log(`[STORE] Removed download ${id}`)
|
||||||
return true
|
success = true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to remove download in store', error)
|
console.error('Failed to remove download in store', error)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
this.releaseLock()
|
||||||
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllDownloads() {
|
async getAllDownloads() {
|
||||||
|
if (this.isLocked) {
|
||||||
|
await this.initWaitLock('alldl')
|
||||||
|
} else {
|
||||||
|
this.setLock('alldl')
|
||||||
|
}
|
||||||
|
|
||||||
if (!(await this.ensureTable('downloads'))) {
|
if (!(await this.ensureTable('downloads'))) {
|
||||||
|
this.releaseLock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,7 +416,7 @@ class StoreService {
|
||||||
try {
|
try {
|
||||||
var download = JSON.parse(keysvalues[i].value)
|
var download = JSON.parse(keysvalues[i].value)
|
||||||
if (!download.id) {
|
if (!download.id) {
|
||||||
console.error('[SqlStore] Removing invalid download')
|
console.error('[SqlStore] Removing invalid download', JSON.stringify(download))
|
||||||
await this.removeItem(keysvalues[i].key)
|
await this.removeItem(keysvalues[i].key)
|
||||||
} else {
|
} else {
|
||||||
downloads.push(download)
|
downloads.push(download)
|
||||||
|
@ -347,45 +427,71 @@ class StoreService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.releaseLock()
|
||||||
return downloads
|
return downloads
|
||||||
}
|
}
|
||||||
|
|
||||||
async setUserAudiobookData(userAudiobookData) {
|
async setUserAudiobookData(userAudiobookData) {
|
||||||
|
if (this.isLocked) {
|
||||||
|
await this.initWaitLock('setuad')
|
||||||
|
} else {
|
||||||
|
this.setLock('setuad')
|
||||||
|
}
|
||||||
|
|
||||||
if (!(await this.ensureTable('userAudiobookData'))) {
|
if (!(await this.ensureTable('userAudiobookData'))) {
|
||||||
|
this.releaseLock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var success = false
|
||||||
try {
|
try {
|
||||||
await this.setItem(userAudiobookData.audiobookId, JSON.stringify(userAudiobookData))
|
await this.setItem(userAudiobookData.audiobookId, JSON.stringify(userAudiobookData))
|
||||||
this.vuexStore.commit('user/setUserAudiobookData', userAudiobookData)
|
this.vuexStore.commit('user/setUserAudiobookData', userAudiobookData)
|
||||||
|
|
||||||
console.log(`[STORE] Set UserAudiobookData ${userAudiobookData.audiobookId}`)
|
console.log(`[STORE] Set UserAudiobookData ${userAudiobookData.audiobookId}`)
|
||||||
return true
|
success = true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to set UserAudiobookData in store', error)
|
console.error('Failed to set UserAudiobookData in store', error)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
this.releaseLock()
|
||||||
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeUserAudiobookData(audiobookId) {
|
async removeUserAudiobookData(audiobookId) {
|
||||||
|
if (this.isLocked) {
|
||||||
|
await this.initWaitLock('remuad')
|
||||||
|
} else {
|
||||||
|
this.setLock('remuad')
|
||||||
|
}
|
||||||
|
|
||||||
if (!(await this.ensureTable('userAudiobookData'))) {
|
if (!(await this.ensureTable('userAudiobookData'))) {
|
||||||
|
this.releaseLock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var success = false
|
||||||
try {
|
try {
|
||||||
await this.removeItem(audiobookId)
|
await this.removeItem(audiobookId)
|
||||||
this.vuexStore.commit('user/removeUserAudiobookData', audiobookId)
|
this.vuexStore.commit('user/removeUserAudiobookData', audiobookId)
|
||||||
|
|
||||||
console.log(`[STORE] Removed userAudiobookData ${id}`)
|
console.log(`[STORE] Removed userAudiobookData ${id}`)
|
||||||
return true
|
success = true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to remove userAudiobookData in store', error)
|
console.error('Failed to remove userAudiobookData in store', error)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
this.releaseLock()
|
||||||
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllUserAudiobookData() {
|
async getAllUserAudiobookData() {
|
||||||
|
if (this.isLocked) {
|
||||||
|
await this.initWaitLock('alluad')
|
||||||
|
} else {
|
||||||
|
this.setLock('alluad')
|
||||||
|
}
|
||||||
|
|
||||||
if (!(await this.ensureTable('userAudiobookData'))) {
|
if (!(await this.ensureTable('userAudiobookData'))) {
|
||||||
|
this.releaseLock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,11 +512,21 @@ class StoreService {
|
||||||
await this.removeItem(keysvalues[i].key)
|
await this.removeItem(keysvalues[i].key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[SqlStore] All UAD finished')
|
||||||
|
this.releaseLock()
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAllUserAudiobookData(userAbData) {
|
async setAllUserAudiobookData(userAbData) {
|
||||||
|
if (this.isLocked) {
|
||||||
|
await this.initWaitLock('setuad')
|
||||||
|
} else {
|
||||||
|
this.setLock('setuad')
|
||||||
|
}
|
||||||
|
|
||||||
if (!(await this.ensureTable('userAudiobookData'))) {
|
if (!(await this.ensureTable('userAudiobookData'))) {
|
||||||
|
this.releaseLock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,6 +547,7 @@ class StoreService {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.vuexStore.commit('user/setAllUserAudiobookData', userAbData)
|
this.vuexStore.commit('user/setAllUserAudiobookData', userAbData)
|
||||||
|
this.releaseLock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,8 @@
|
||||||
const STANDARD_GENRES = ['adventure', 'autobiography', 'biography', 'childrens', 'comedy', 'crime', 'dystopian', 'fantasy', 'fiction', 'health', 'history', 'horror', 'mystery', 'new_adult', 'nonfiction', 'philosophy', 'politics', 'religion', 'romance', 'sci-fi', 'self-help', 'short_story', 'technology', 'thriller', 'true_crime', 'western', 'young_adult']
|
|
||||||
|
|
||||||
export const state = () => ({
|
export const state = () => ({
|
||||||
audiobooks: [],
|
|
||||||
listeners: [],
|
|
||||||
loadedLibraryId: 'main',
|
|
||||||
lastLoad: 0,
|
|
||||||
isLoading: false
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
getAudiobook: state => id => {
|
|
||||||
return state.audiobooks.find(ab => ab.id === id)
|
|
||||||
},
|
|
||||||
getBookCoverSrc: (state, getters, rootState, rootGetters) => (bookItem, placeholder = '/book_placeholder.jpg') => {
|
getBookCoverSrc: (state, getters, rootState, rootGetters) => (bookItem, placeholder = '/book_placeholder.jpg') => {
|
||||||
var book = bookItem.book
|
var book = bookItem.book
|
||||||
if (!book || !book.cover || book.cover === placeholder) return placeholder
|
if (!book || !book.cover || book.cover === placeholder) return placeholder
|
||||||
|
@ -44,125 +35,9 @@ export const getters = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
useDownloaded({ commit, rootGetters }) {
|
|
||||||
commit('set', rootGetters['downloads/getAudiobooks'])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
setLoadedLibrary(state, val) {
|
|
||||||
state.loadedLibraryId = val
|
|
||||||
},
|
|
||||||
setLoading(state, val) {
|
|
||||||
state.isLoading = val
|
|
||||||
},
|
|
||||||
setLastLoad(state, val) {
|
|
||||||
state.lastLoad = val
|
|
||||||
},
|
|
||||||
reset(state) {
|
|
||||||
state.audiobooks = []
|
|
||||||
state.genres = [...STANDARD_GENRES]
|
|
||||||
state.tags = []
|
|
||||||
state.series = []
|
|
||||||
},
|
|
||||||
addUpdate(state, audiobook) {
|
|
||||||
var index = state.audiobooks.findIndex(a => a.id === audiobook.id)
|
|
||||||
var origAudiobook = null
|
|
||||||
if (index >= 0) {
|
|
||||||
origAudiobook = { ...state.audiobooks[index] }
|
|
||||||
state.audiobooks.splice(index, 1, audiobook)
|
|
||||||
} else {
|
|
||||||
state.audiobooks.push(audiobook)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audiobook.book) {
|
|
||||||
// GENRES
|
|
||||||
var newGenres = []
|
|
||||||
audiobook.book.genres.forEach((genre) => {
|
|
||||||
if (!state.genres.includes(genre)) newGenres.push(genre)
|
|
||||||
})
|
|
||||||
if (newGenres.length) {
|
|
||||||
state.genres = state.genres.concat(newGenres)
|
|
||||||
state.genres.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SERIES
|
|
||||||
if (audiobook.book.series && !state.series.includes(audiobook.book.series)) {
|
|
||||||
state.series.push(audiobook.book.series)
|
|
||||||
state.series.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
|
|
||||||
}
|
|
||||||
if (origAudiobook && origAudiobook.book && origAudiobook.book.series) {
|
|
||||||
var isInAB = state.audiobooks.find(ab => ab.book && ab.book.series === origAudiobook.book.series)
|
|
||||||
if (!isInAB) state.series = state.series.filter(series => series !== origAudiobook.book.series)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TAGS
|
|
||||||
var newTags = []
|
|
||||||
audiobook.tags.forEach((tag) => {
|
|
||||||
if (!state.tags.includes(tag)) newTags.push(tag)
|
|
||||||
})
|
|
||||||
if (newTags.length) {
|
|
||||||
state.tags = state.tags.concat(newTags)
|
|
||||||
state.tags.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
state.listeners.forEach((listener) => {
|
|
||||||
if (!listener.audiobookId || listener.audiobookId === audiobook.id) {
|
|
||||||
listener.meth()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
remove(state, audiobook) {
|
|
||||||
state.audiobooks = state.audiobooks.filter(a => a.id !== audiobook.id)
|
|
||||||
|
|
||||||
if (audiobook.book) {
|
|
||||||
// GENRES
|
|
||||||
audiobook.book.genres.forEach((genre) => {
|
|
||||||
if (!STANDARD_GENRES.includes(genre)) {
|
|
||||||
var isInOtherAB = state.audiobooks.find(ab => {
|
|
||||||
return ab.book && ab.book.genres.includes(genre)
|
|
||||||
})
|
|
||||||
if (!isInOtherAB) {
|
|
||||||
// Genre is not used by any other audiobook - remove it
|
|
||||||
state.genres = state.genres.filter(g => g !== genre)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// SERIES
|
|
||||||
if (audiobook.book.series) {
|
|
||||||
var isInOtherAB = state.audiobooks.find(ab => ab.book && ab.book.series === audiobook.book.series)
|
|
||||||
if (!isInOtherAB) {
|
|
||||||
// Series not used in any other audiobook - remove it
|
|
||||||
state.series = state.series.filter(s => s !== audiobook.book.series)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TAGS
|
|
||||||
audiobook.tags.forEach((tag) => {
|
|
||||||
var isInOtherAB = state.audiobooks.find(ab => {
|
|
||||||
return ab.tags.includes(tag)
|
|
||||||
})
|
|
||||||
if (!isInOtherAB) {
|
|
||||||
// Tag is not used by any other audiobook - remove it
|
|
||||||
state.tags = state.tags.filter(t => t !== tag)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
state.listeners.forEach((listener) => {
|
|
||||||
if (!listener.audiobookId || listener.audiobookId === audiobook.id) {
|
|
||||||
listener.meth()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
addListener(state, listener) {
|
|
||||||
var index = state.listeners.findIndex(l => l.id === listener.id)
|
|
||||||
if (index >= 0) state.listeners.splice(index, 1, listener)
|
|
||||||
else state.listeners.push(listener)
|
|
||||||
},
|
|
||||||
removeListener(state, listenerId) {
|
|
||||||
state.listeners = state.listeners.filter(l => l.id !== listenerId)
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -10,6 +10,9 @@ export const getters = {
|
||||||
getDownload: (state) => id => {
|
getDownload: (state) => id => {
|
||||||
return state.downloads.find(d => d.id === id)
|
return state.downloads.find(d => d.id === id)
|
||||||
},
|
},
|
||||||
|
getDownloads: state => {
|
||||||
|
return state.downloads
|
||||||
|
},
|
||||||
getDownloadIfReady: (state) => id => {
|
getDownloadIfReady: (state) => id => {
|
||||||
var download = state.downloads.find(d => d.id === id)
|
var download = state.downloads.find(d => d.id === id)
|
||||||
return !!download && !download.isDownloading && !download.isPreparing ? download : null
|
return !!download && !download.isDownloading && !download.isPreparing ? download : null
|
||||||
|
@ -22,7 +25,7 @@ export const getters = {
|
||||||
export const actions = {
|
export const actions = {
|
||||||
async loadFromStorage({ commit, state }) {
|
async loadFromStorage({ commit, state }) {
|
||||||
var downloads = await this.$sqlStore.getAllDownloads()
|
var downloads = await this.$sqlStore.getAllDownloads()
|
||||||
|
console.log('Load downloads from storage', downloads.length)
|
||||||
downloads.forEach(ab => {
|
downloads.forEach(ab => {
|
||||||
if (ab.isDownloading || ab.isPreparing) {
|
if (ab.isDownloading || ab.isPreparing) {
|
||||||
ab.isIncomplete = true
|
ab.isIncomplete = true
|
||||||
|
@ -37,27 +40,23 @@ export const actions = {
|
||||||
if (!state.mediaScanResults || !state.mediaScanResults.folders) {
|
if (!state.mediaScanResults || !state.mediaScanResults.folders) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log('Link orphan downloads', JSON.stringify(state.mediaScanResults.folders))
|
|
||||||
// state.mediaScanResults.folders.forEach((folder) => {
|
|
||||||
for (let i = 0; i < state.mediaScanResults.folders.length; i++) {
|
for (let i = 0; i < state.mediaScanResults.folders.length; i++) {
|
||||||
var folder = state.mediaScanResults.folders[i]
|
var folder = state.mediaScanResults.folders[i]
|
||||||
if (!folder.files || !folder.files.length) return
|
if (!folder.files || !folder.files.length) return
|
||||||
|
|
||||||
console.log('Link orphan downloads check folder', folder.name)
|
|
||||||
var download = state.downloads.find(dl => dl.folderName === folder.name)
|
var download = state.downloads.find(dl => dl.folderName === folder.name)
|
||||||
if (!download) {
|
if (!download) {
|
||||||
// var matchingAb = audiobooks.find(ab => ab.book.title === folder.name)
|
var results = await this.$axios.$get(`/api/libraries/${rootState.libraries.currentLibraryId}/search?q=${folder.name}`)
|
||||||
var results = await this.$axios.$get(`/libraries/${rootState.libraries.currentLibraryId}/search?q=${folder.name}`)
|
|
||||||
var matchingAb = null
|
var matchingAb = null
|
||||||
if (results && results.audiobooks) {
|
if (results && results.audiobooks) {
|
||||||
console.log('has ab results', JSON.stringify(results.audiobooks))
|
matchingAb = results.audiobooks.find(ab => {
|
||||||
matchingAb = results.audiobooks.find(ab => ab.audiobook.book.title === folder.name)
|
return ab.audiobook.book.title === folder.name
|
||||||
if (matchingAb) console.log('Found matching ab for ' + folder.name, matchingAb)
|
})
|
||||||
else console.warn('did not find mathcing ab for ' + folder.name)
|
|
||||||
} else {
|
|
||||||
console.error('Invalid results payload', JSON.stringify(results))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchingAb) {
|
if (matchingAb) {
|
||||||
|
matchingAb = matchingAb.audiobook
|
||||||
// Found matching download for ab
|
// Found matching download for ab
|
||||||
var audioFile = folder.files.find(f => f.isAudio)
|
var audioFile = folder.files.find(f => f.isAudio)
|
||||||
if (!audioFile) {
|
if (!audioFile) {
|
||||||
|
@ -80,7 +79,6 @@ export const actions = {
|
||||||
coverSize: coverImg ? coverImg.size : 0,
|
coverSize: coverImg ? coverImg.size : 0,
|
||||||
coverBasePath: ''
|
coverBasePath: ''
|
||||||
}
|
}
|
||||||
console.log('Linking orphan download: ' + JSON.stringify(downloadObj))
|
|
||||||
commit('addUpdateDownload', downloadObj)
|
commit('addUpdateDownload', downloadObj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,6 +103,7 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
addUpdateDownload(state, download) {
|
addUpdateDownload(state, download) {
|
||||||
if (!download || !download.id) {
|
if (!download || !download.id) {
|
||||||
|
console.error('Orphan invalid download ' + download.id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var index = state.downloads.findIndex(d => d.id === download.id)
|
var index = state.downloads.findIndex(d => d.id === download.id)
|
||||||
|
|
|
@ -135,5 +135,6 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
setServerSettings(state, val) {
|
setServerSettings(state, val) {
|
||||||
state.serverSettings = val
|
state.serverSettings = val
|
||||||
|
this.$localStore.setServerSettings(state.serverSettings)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -136,4 +136,45 @@ export const mutations = {
|
||||||
setLibraryFilterData(state, filterData) {
|
setLibraryFilterData(state, filterData) {
|
||||||
state.filterData = filterData
|
state.filterData = filterData
|
||||||
},
|
},
|
||||||
|
updateFilterDataWithAudiobook(state, audiobook) {
|
||||||
|
if (!audiobook || !audiobook.book || !state.filterData) return
|
||||||
|
if (state.currentLibraryId !== audiobook.libraryId) return
|
||||||
|
/*
|
||||||
|
var filterdata = {
|
||||||
|
authors: [],
|
||||||
|
genres: [],
|
||||||
|
tags: [],
|
||||||
|
series: [],
|
||||||
|
narrators: []
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (audiobook.book.authorFL) {
|
||||||
|
audiobook.book.authorFL.split(', ').forEach((author) => {
|
||||||
|
if (author && !state.filterData.authors.includes(author)) {
|
||||||
|
state.filterData.authors.push(author)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (audiobook.book.narratorFL) {
|
||||||
|
audiobook.book.narratorFL.split(', ').forEach((narrator) => {
|
||||||
|
if (narrator && !state.filterData.narrators.includes(narrator)) {
|
||||||
|
state.filterData.narrators.push(narrator)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (audiobook.book.series && !state.filterData.series.includes(audiobook.book.series)) {
|
||||||
|
state.filterData.series.push(audiobook.book.series)
|
||||||
|
}
|
||||||
|
if (audiobook.tags && audiobook.tags.length) {
|
||||||
|
audiobook.tags.forEach((tag) => {
|
||||||
|
if (tag && !state.filterData.tags.includes(tag)) state.filterData.tags.push(tag)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (audiobook.book.genres && audiobook.book.genres.length) {
|
||||||
|
audiobook.book.genres.forEach((genre) => {
|
||||||
|
if (genre && !state.filterData.genres.includes(genre)) state.filterData.genres.push(genre)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -12,9 +12,7 @@ export const state = () => ({
|
||||||
bookshelfCoverSize: 120
|
bookshelfCoverSize: 120
|
||||||
},
|
},
|
||||||
settingsListeners: [],
|
settingsListeners: [],
|
||||||
userAudiobooksListeners: [],
|
userAudiobooksListeners: []
|
||||||
collections: [],
|
|
||||||
collectionsLoaded: false
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
|
@ -33,9 +31,6 @@ export const getters = {
|
||||||
},
|
},
|
||||||
getFilterOrderKey: (state) => {
|
getFilterOrderKey: (state) => {
|
||||||
return Object.values(state.settings).join('-')
|
return Object.values(state.settings).join('-')
|
||||||
},
|
|
||||||
getCollection: state => id => {
|
|
||||||
return state.collections.find(c => c.id === id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,23 +56,14 @@ export const actions = {
|
||||||
commit('setSettings', payload)
|
commit('setSettings', payload)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
loadUserCollections({ state, commit }) {
|
async loadOfflineUserAudiobookData({ state, commit }) {
|
||||||
if (!this.$server.connected) {
|
var localUserAudiobookData = await this.$sqlStore.getAllUserAudiobookData() || []
|
||||||
console.error('Not loading collections - not connected')
|
if (localUserAudiobookData.length) {
|
||||||
return []
|
console.log('loadOfflineUserAudiobookData found', localUserAudiobookData.length, 'user audiobook data')
|
||||||
|
commit('setAllUserAudiobookData', localUserAudiobookData)
|
||||||
|
} else {
|
||||||
|
console.log('loadOfflineUserAudiobookData No user audiobook data')
|
||||||
}
|
}
|
||||||
if (state.collectionsLoaded) {
|
|
||||||
console.log('Collections already loaded')
|
|
||||||
return state.collections
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.$axios.$get('/api/collections').then((collections) => {
|
|
||||||
commit('setCollections', collections)
|
|
||||||
return collections
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error('Failed to get collections', error)
|
|
||||||
return []
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
async syncUserAudiobookData({ state, commit }) {
|
async syncUserAudiobookData({ state, commit }) {
|
||||||
if (!state.user) {
|
if (!state.user) {
|
||||||
|
@ -167,17 +153,5 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
removeUserAudiobookListener(state, listenerId) {
|
removeUserAudiobookListener(state, listenerId) {
|
||||||
state.userAudiobooksListeners = state.userAudiobooksListeners.filter(l => l.id !== listenerId)
|
state.userAudiobooksListeners = state.userAudiobooksListeners.filter(l => l.id !== listenerId)
|
||||||
},
|
|
||||||
setCollections(state, collections) {
|
|
||||||
state.collections = collections
|
|
||||||
state.collectionsLoaded = true
|
|
||||||
},
|
|
||||||
addUpdateCollection(state, collection) {
|
|
||||||
var index = state.collections.findIndex(c => c.id === collection.id)
|
|
||||||
if (index >= 0) {
|
|
||||||
state.collections.splice(index, 1, collection)
|
|
||||||
} else {
|
|
||||||
state.collections.push(collection)
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue