mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-30 23:54:30 +02:00
UI updates
This commit is contained in:
parent
12a153d423
commit
abf140bd21
10 changed files with 142 additions and 51 deletions
|
@ -6,13 +6,13 @@
|
|||
</div>
|
||||
|
||||
<!-- Alternative bookshelf title/author/sort -->
|
||||
<div v-if="isAlternativeBookshelfView" class="absolute left-0 z-50 w-full" :style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
|
||||
<!-- <div v-if="isAlternativeBookshelfView" class="absolute left-0 z-50 w-full" :style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
|
||||
<p class="truncate" :style="{ fontSize: 0.9 * sizeMultiplier + 'rem' }">
|
||||
{{ displayTitle }}
|
||||
</p>
|
||||
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayAuthor }}</p>
|
||||
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div v-if="booksInSeries" class="absolute z-20 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center" style="background-color: #cd9d49dd">{{ booksInSeries }}</div>
|
||||
|
||||
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
|
||||
<!-- No progress shown for collapsed series in library -->
|
||||
<div v-if="!booksInSeries" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
||||
<div v-if="!collapsedSeries && !isPodcast" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
||||
|
||||
<div v-if="localLibraryItem || isLocal" class="absolute top-0 right-0 z-20" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||
<span class="material-icons text-2xl text-success">{{ isLocalOnly ? 'task' : 'download_done' }}</span>
|
||||
|
@ -49,8 +49,13 @@
|
|||
</ui-tooltip>
|
||||
|
||||
<!-- Volume number -->
|
||||
<div v-if="volumeNumber && showVolumeNumber && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ volumeNumber }}</p>
|
||||
<div v-if="seriesSequence && showSequence && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequence }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Podcast Num Episodes -->
|
||||
<div v-if="numEpisodes && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -70,7 +75,7 @@ export default {
|
|||
default: 192
|
||||
},
|
||||
bookCoverAspectRatio: Number,
|
||||
showVolumeNumber: Boolean,
|
||||
showSequence: Boolean,
|
||||
bookshelfView: Number,
|
||||
bookMount: {
|
||||
// Book can be passed as prop or set with setEntity()
|
||||
|
@ -122,6 +127,12 @@ export default {
|
|||
mediaMetadata() {
|
||||
return this.media.metadata || {}
|
||||
},
|
||||
mediaType() {
|
||||
return this._libraryItem.mediaType
|
||||
},
|
||||
isPodcast() {
|
||||
return this.mediaType === 'podcast'
|
||||
},
|
||||
placeholderUrl() {
|
||||
return '/book_placeholder.jpg'
|
||||
},
|
||||
|
@ -135,9 +146,6 @@ export default {
|
|||
libraryItemId() {
|
||||
return this._libraryItem.id
|
||||
},
|
||||
series() {
|
||||
return this.mediaMetadata.series
|
||||
},
|
||||
libraryId() {
|
||||
return this._libraryItem.libraryId
|
||||
},
|
||||
|
@ -147,6 +155,9 @@ export default {
|
|||
numTracks() {
|
||||
return this.media.numTracks
|
||||
},
|
||||
numEpisodes() {
|
||||
return this.media.numEpisodes
|
||||
},
|
||||
processingBatch() {
|
||||
return this.store.state.processingBatch
|
||||
},
|
||||
|
@ -174,19 +185,26 @@ export default {
|
|||
return this.mediaMetadata.authors || []
|
||||
},
|
||||
author() {
|
||||
return this.authors.map((au) => au.name).join(', ')
|
||||
if (this.isPodcast) return this.mediaMetadata.author
|
||||
return this.mediaMetadata.authorName
|
||||
},
|
||||
authorLF() {
|
||||
return this.authors
|
||||
.map((au) => {
|
||||
var parts = au.name.split(' ')
|
||||
if (parts.length === 1) return parts[0]
|
||||
return `${parts[1]}, ${parts[0]}`
|
||||
})
|
||||
.join(', ')
|
||||
return this.mediaMetadata.authorNameLF
|
||||
},
|
||||
volumeNumber() {
|
||||
return this.mediaMetadata.volumeNumber || null
|
||||
series() {
|
||||
// Only included when filtering by series or collapse series
|
||||
return this.mediaMetadata.series
|
||||
},
|
||||
seriesSequence() {
|
||||
return this.series ? this.series.sequence : null
|
||||
},
|
||||
collapsedSeries() {
|
||||
// Only added to item object when collapseSeries is enabled
|
||||
return this._libraryItem.collapsedSeries
|
||||
},
|
||||
booksInSeries() {
|
||||
// Only added to item object when collapseSeries is enabled
|
||||
return this.collapsedSeries ? this.collapsedSeries.numBooks : 0
|
||||
},
|
||||
displayTitle() {
|
||||
if (this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix && this.title.toLowerCase().startsWith('the ')) {
|
||||
|
@ -308,16 +326,6 @@ export default {
|
|||
return this.author.slice(0, 27) + '...'
|
||||
}
|
||||
return this.author
|
||||
},
|
||||
isAlternativeBookshelfView() {
|
||||
return false
|
||||
// var constants = this.$constants || this.$nuxt.$constants
|
||||
// return this.bookshelfView === constants.BookshelfView.TITLES
|
||||
},
|
||||
titleDisplayBottomOffset() {
|
||||
if (!this.isAlternativeBookshelfView) return 0
|
||||
else if (!this.displaySortLine) return 3 * this.sizeMultiplier
|
||||
return 4.25 * this.sizeMultiplier
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -340,7 +348,7 @@ export default {
|
|||
} else {
|
||||
var router = this.$router || this.$nuxt.$router
|
||||
if (router) {
|
||||
if (this.booksInSeries) router.push(`/library/${this.libraryId}/series/${this.$encode(this.series)}`)
|
||||
if (this.collapsedSeries) router.push(`/library/${this.libraryId}/series/${this.collapsedSeries.id}`)
|
||||
else router.push(`/item/${this.libraryItemId}`)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
<img v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
|
||||
</div>
|
||||
|
||||
<!-- No progress shown for collapsed series in library -->
|
||||
<div class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
||||
<!-- No progress shown for collapsed series or podcasts in library -->
|
||||
<div v-if="!isPodcast && !collapsedSeries" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: 80 * userProgressPercent + 'px' }"></div>
|
||||
|
||||
<div v-if="localLibraryItem || isLocal" class="absolute top-0 right-0 z-20" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||
<span class="material-icons text-2xl text-success">{{ isLocalOnly ? 'task' : 'download_done' }}</span>
|
||||
|
@ -20,7 +20,7 @@
|
|||
</div>
|
||||
<div class="flex-grow px-2">
|
||||
<p class="whitespace-normal" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">
|
||||
{{ displayTitle }}
|
||||
{{ displayTitle }}<span v-if="seriesSequence"> #{{ seriesSequence }}</span>
|
||||
</p>
|
||||
<p class="truncate text-gray-400" :style="{ fontSize: 0.7 * sizeMultiplier + 'rem' }">{{ displayAuthor }}</p>
|
||||
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.7 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
|
||||
|
@ -44,7 +44,7 @@ export default {
|
|||
default: 192
|
||||
},
|
||||
bookCoverAspectRatio: Number,
|
||||
showVolumeNumber: Boolean,
|
||||
showSequence: Boolean,
|
||||
bookshelfView: Number,
|
||||
bookMount: {
|
||||
// Book can be passed as prop or set with setEntity()
|
||||
|
@ -96,6 +96,12 @@ export default {
|
|||
mediaMetadata() {
|
||||
return this.media.metadata || {}
|
||||
},
|
||||
mediaType() {
|
||||
return this._libraryItem.mediaType
|
||||
},
|
||||
isPodcast() {
|
||||
return this.mediaType === 'podcast'
|
||||
},
|
||||
placeholderUrl() {
|
||||
return '/book_placeholder.jpg'
|
||||
},
|
||||
|
@ -149,8 +155,20 @@ export default {
|
|||
authorLF() {
|
||||
return this.mediaMetadata.authorNameLF || ''
|
||||
},
|
||||
volumeNumber() {
|
||||
return this.mediaMetadata.volumeNumber || null
|
||||
series() {
|
||||
// Only included when filtering by series or collapse series
|
||||
return this.mediaMetadata.series
|
||||
},
|
||||
seriesSequence() {
|
||||
return this.series ? this.series.sequence : null
|
||||
},
|
||||
collapsedSeries() {
|
||||
// Only added to item object when collapseSeries is enabled
|
||||
return this._libraryItem.collapsedSeries
|
||||
},
|
||||
booksInSeries() {
|
||||
// Only added to item object when collapseSeries is enabled
|
||||
return this.collapsedSeries ? this.collapsedSeries.numBooks : 0
|
||||
},
|
||||
displayTitle() {
|
||||
if (this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix && this.title.toLowerCase().startsWith('the ')) {
|
||||
|
@ -303,7 +321,7 @@ export default {
|
|||
} else {
|
||||
var router = this.$router || this.$nuxt.$router
|
||||
if (router) {
|
||||
if (this.booksInSeries) router.push(`/library/${this.libraryId}/series/${this.$encode(this.series)}`)
|
||||
if (this.collapsedSeries) router.push(`/library/${this.libraryId}/series/${this.collapsedSeries.id}`)
|
||||
else router.push(`/item/${this.libraryItemId}`)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,8 @@
|
|||
<template>
|
||||
<div class="w-full h-9 bg-bg relative">
|
||||
<div id="bookshelf-navbar" class="absolute z-10 top-0 left-0 w-full h-full flex bg-secondary text-gray-200">
|
||||
<nuxt-link to="/bookshelf" class="w-1/4 h-full flex items-center justify-center" :class="routeName === 'bookshelf' ? 'bg-primary' : 'text-gray-400'">
|
||||
<p>Home</p>
|
||||
</nuxt-link>
|
||||
<nuxt-link to="/bookshelf/library" class="w-1/4 h-full flex items-center justify-center" :class="routeName === 'bookshelf-library' ? 'bg-primary' : 'text-gray-400'">
|
||||
<p>Library</p>
|
||||
</nuxt-link>
|
||||
<nuxt-link to="/bookshelf/series" class="w-1/4 h-full flex items-center justify-center" :class="routeName === 'bookshelf-series' ? 'bg-primary' : 'text-gray-400'">
|
||||
<p>Series</p>
|
||||
</nuxt-link>
|
||||
<nuxt-link to="/bookshelf/collections" class="w-1/4 h-full flex items-center justify-center" :class="routeName === 'bookshelf-collections' ? 'bg-primary' : 'text-gray-400'">
|
||||
<p>Collections</p>
|
||||
<nuxt-link v-for="item in items" :key="item.to" :to="item.to" class="h-full flex items-center justify-center" :style="{ width: isPodcast ? '50%' : '25%' }" :class="routeName === item.routeName ? 'bg-primary' : 'text-gray-400'">
|
||||
<p>{{ item.text }}</p>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -23,8 +14,52 @@ export default {
|
|||
return {}
|
||||
},
|
||||
computed: {
|
||||
items() {
|
||||
if (this.isPodcast) {
|
||||
return [
|
||||
{
|
||||
to: '/bookshelf',
|
||||
routeName: 'bookshelf',
|
||||
text: 'Home'
|
||||
},
|
||||
{
|
||||
to: '/bookshelf/library',
|
||||
routeName: 'bookshelf-library',
|
||||
text: 'Library'
|
||||
}
|
||||
]
|
||||
}
|
||||
return [
|
||||
{
|
||||
to: '/bookshelf',
|
||||
routeName: 'bookshelf',
|
||||
text: 'Home'
|
||||
},
|
||||
{
|
||||
to: '/bookshelf/library',
|
||||
routeName: 'bookshelf-library',
|
||||
text: 'Library'
|
||||
},
|
||||
{
|
||||
to: '/bookshelf/series',
|
||||
routeName: 'bookshelf-series',
|
||||
text: 'Series'
|
||||
},
|
||||
{
|
||||
to: '/bookshelf/collections',
|
||||
routeName: 'bookshelf-collections',
|
||||
text: 'Collections'
|
||||
}
|
||||
]
|
||||
},
|
||||
routeName() {
|
||||
return this.$route.name
|
||||
},
|
||||
isPodcast() {
|
||||
return this.libraryMediaType == 'podcast'
|
||||
},
|
||||
libraryMediaType() {
|
||||
return this.$store.getters['libraries/getCurrentLibraryMediaType']
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
<p v-show="!selectedSeriesName" class="font-book pt-1">{{ totalEntities }} {{ entityTitle }}</p>
|
||||
<p v-show="selectedSeriesName" class="ml-2 font-book pt-1">{{ selectedSeriesName }} ({{ totalEntities }})</p>
|
||||
<div class="flex-grow" />
|
||||
<span v-if="page == 'library' || seriesBookPage" class="material-icons px-2" @click="bookshelfListView = !bookshelfListView">{{ bookshelfListView ? 'view_list' : 'grid_view' }}</span>
|
||||
<template v-if="page === 'library'">
|
||||
<span class="material-icons px-2" @click="bookshelfListView = !bookshelfListView">{{ bookshelfListView ? 'view_list' : 'grid_view' }}</span>
|
||||
<div class="relative flex items-center px-2">
|
||||
<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" />
|
||||
|
@ -51,6 +51,9 @@ export default {
|
|||
var routeName = this.$route.name || ''
|
||||
return routeName.split('-')[1]
|
||||
},
|
||||
seriesBookPage() {
|
||||
return this.$route.name == 'bookshelf-series-id'
|
||||
},
|
||||
routeQuery() {
|
||||
return this.$route.query || {}
|
||||
},
|
||||
|
|
|
@ -274,6 +274,12 @@ export default {
|
|||
} else {
|
||||
console.log('[default] syncLocalMediaProgress No local media progress to sync')
|
||||
}
|
||||
},
|
||||
userUpdated(user) {
|
||||
console.log('User updated', user)
|
||||
if (this.user && this.user.id == user.id) {
|
||||
this.$store.commit('user/setUser', user)
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
|
@ -286,6 +292,7 @@ export default {
|
|||
|
||||
this.$socket.on('connection-update', this.socketConnectionUpdate)
|
||||
this.$socket.on('initialized', this.socketInit)
|
||||
this.$socket.on('user_updated', this.userUpdated)
|
||||
|
||||
if (this.$store.state.isFirstLoad) {
|
||||
this.$store.commit('setIsFirstLoad', false)
|
||||
|
@ -310,6 +317,7 @@ export default {
|
|||
beforeDestroy() {
|
||||
this.$socket.off('connection-update', this.socketConnectionUpdate)
|
||||
this.$socket.off('initialized', this.socketInit)
|
||||
this.$socket.off('user_updated', this.userUpdated)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -47,7 +47,7 @@ export default {
|
|||
height: this.entityHeight,
|
||||
bookCoverAspectRatio: this.bookCoverAspectRatio
|
||||
}
|
||||
if (this.entityName === 'series-books') props.showVolumeNumber = true
|
||||
if (this.entityName === 'series-books') props.showSequence = true
|
||||
|
||||
var _this = this
|
||||
var instance = new ComponentClass({
|
||||
|
|
|
@ -123,7 +123,13 @@ export default {
|
|||
}
|
||||
return cat
|
||||
})
|
||||
this.shelves = this.shelves.concat(categories)
|
||||
// Put continue listening shelf first
|
||||
var continueListeningShelf = categories.find((c) => c.id == 'continue-listening')
|
||||
if (continueListeningShelf) {
|
||||
this.shelves = [continueListeningShelf, ...this.shelves]
|
||||
console.log(this.shelves)
|
||||
}
|
||||
this.shelves = this.shelves.concat(categories.filter((c) => c.id != 'continue-listening'))
|
||||
}
|
||||
this.loading = false
|
||||
},
|
||||
|
|
|
@ -240,14 +240,17 @@ export default {
|
|||
if (value) {
|
||||
this.resettingProgress = true
|
||||
if (this.isLocal) {
|
||||
// TODO: If connected to server also sync with server
|
||||
await this.$db.removeLocalMediaProgress(this.libraryItemId)
|
||||
this.$store.commit('globals/removeLocalMediaProgress', this.libraryItemId)
|
||||
} else {
|
||||
var progressId = this.userItemProgress.id
|
||||
await this.$axios
|
||||
.$delete(`/api/me/progress/${this.libraryItemId}`)
|
||||
.then(() => {
|
||||
console.log('Progress reset complete')
|
||||
this.$toast.success(`Your progress was reset`)
|
||||
this.$store.commit('user/removeMediaProgress', progressId)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Progress reset failed', error)
|
||||
|
|
|
@ -46,6 +46,7 @@ class ServerSocket extends EventEmitter {
|
|||
this.socket.on('connect', this.onConnect.bind(this))
|
||||
this.socket.on('disconnect', this.onDisconnect.bind(this))
|
||||
this.socket.on('init', this.onInit.bind(this))
|
||||
this.socket.on('user_updated', this.onUserUpdated.bind(this))
|
||||
|
||||
this.socket.onAny((evt, args) => {
|
||||
console.log(`[SOCKET] ${this.socket.id}: ${evt} ${JSON.stringify(args)}`)
|
||||
|
@ -78,6 +79,11 @@ class ServerSocket extends EventEmitter {
|
|||
}
|
||||
this.emit('initialized', true)
|
||||
}
|
||||
|
||||
onUserUpdated(data) {
|
||||
console.log('[SOCKET] User updated', data)
|
||||
this.emit('user_updated', data)
|
||||
}
|
||||
}
|
||||
|
||||
export default ({ app, store }, inject) => {
|
||||
|
|
|
@ -70,6 +70,10 @@ export const mutations = {
|
|||
setUser(state, user) {
|
||||
state.user = user
|
||||
},
|
||||
removeMediaProgress(state, id) {
|
||||
if (!state.user) return
|
||||
state.user.mediaProgress = state.user.mediaProgress.filter(mp => mp.id != id)
|
||||
},
|
||||
setServerConnectionConfig(state, serverConnectionConfig) {
|
||||
state.serverConnectionConfig = serverConnectionConfig
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue