UI updates

This commit is contained in:
advplyr 2022-04-09 20:29:59 -05:00
parent 12a153d423
commit abf140bd21
10 changed files with 142 additions and 51 deletions

View file

@ -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}`)
}
}

View file

@ -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">&nbsp;#{{ 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}`)
}
}

View file

@ -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: {},

View file

@ -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 || {}
},

View file

@ -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>

View file

@ -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({

View file

@ -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
},

View file

@ -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)

View file

@ -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) => {

View file

@ -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
},