mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-03 00:06:46 +01:00
Add:Experimental collapse series for library option #322
This commit is contained in:
parent
5bd6c8f553
commit
d8cc0b57a5
@ -142,14 +142,6 @@ export default {
|
|||||||
this.$eventBus.$emit('bookshelf-clear-selection')
|
this.$eventBus.$emit('bookshelf-clear-selection')
|
||||||
this.isAllSelected = false
|
this.isAllSelected = false
|
||||||
},
|
},
|
||||||
// toggleSelectAll() {
|
|
||||||
// if (this.isAllSelected) {
|
|
||||||
// this.cancelSelectionMode()
|
|
||||||
// } else {
|
|
||||||
// this.$eventBus.$emit('bookshelf-select-all')
|
|
||||||
// this.isAllSelected = true
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
toggleBatchRead() {
|
toggleBatchRead() {
|
||||||
this.$store.commit('setProcessingBatch', true)
|
this.$store.commit('setProcessingBatch', true)
|
||||||
var newIsRead = !this.selectedIsRead
|
var newIsRead = !this.selectedIsRead
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
<div class="flex-grow hidden md:inline-block" />
|
<div class="flex-grow hidden md:inline-block" />
|
||||||
|
|
||||||
<!-- <ui-text-input v-show="showSortFilters" v-model="keywordFilter" @input="keywordFilterInput" placeholder="Keyword Filter" :padding-y="1.5" clearable class="text-xs w-40 hidden md:block" /> -->
|
<!-- <ui-text-input v-show="showSortFilters" v-model="keywordFilter" @input="keywordFilterInput" placeholder="Keyword Filter" :padding-y="1.5" clearable class="text-xs w-40 hidden md:block" /> -->
|
||||||
|
<ui-checkbox v-if="showExperimentalFeatures" v-model="settings.collapseSeries" label="Collapse Series" checkbox-bg="bg" check-color="white" small class="mr-2" @input="updateCollapseSeries" />
|
||||||
<controls-filter-select v-show="showSortFilters" v-model="settings.filterBy" class="w-48 h-7.5 ml-4" @change="updateFilter" />
|
<controls-filter-select v-show="showSortFilters" v-model="settings.filterBy" class="w-48 h-7.5 ml-4" @change="updateFilter" />
|
||||||
<controls-order-select v-show="showSortFilters" v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-48 h-7.5 ml-4" @change="updateOrder" />
|
<controls-order-select v-show="showSortFilters" v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-48 h-7.5 ml-4" @change="updateOrder" />
|
||||||
<!-- <div v-show="showSortFilters" class="h-7 ml-4 flex border border-white border-opacity-25 rounded-md">
|
<!-- <div v-show="showSortFilters" class="h-7 ml-4 flex border border-white border-opacity-25 rounded-md">
|
||||||
@ -72,6 +73,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
showExperimentalFeatures() {
|
||||||
|
return this.$store.state.showExperimentalFeatures
|
||||||
|
},
|
||||||
isGridMode() {
|
isGridMode() {
|
||||||
return this.viewMode === 'grid'
|
return this.viewMode === 'grid'
|
||||||
},
|
},
|
||||||
@ -87,14 +91,6 @@ export default {
|
|||||||
if (this.page === 'collections') return 'Collections'
|
if (this.page === 'collections') return 'Collections'
|
||||||
return ''
|
return ''
|
||||||
},
|
},
|
||||||
// _keywordFilter: {
|
|
||||||
// get() {
|
|
||||||
// return this.$store.state.audiobooks.keywordFilter
|
|
||||||
// },
|
|
||||||
// set(val) {
|
|
||||||
// this.$store.commit('audiobooks/setKeywordFilter', val)
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
paramId() {
|
paramId() {
|
||||||
return this.$route.params ? this.$route.params.id || '' : ''
|
return this.$route.params ? this.$route.params.id || '' : ''
|
||||||
},
|
},
|
||||||
@ -124,6 +120,9 @@ export default {
|
|||||||
updateFilter() {
|
updateFilter() {
|
||||||
this.saveSettings()
|
this.saveSettings()
|
||||||
},
|
},
|
||||||
|
updateCollapseSeries() {
|
||||||
|
this.saveSettings()
|
||||||
|
},
|
||||||
saveSettings() {
|
saveSettings() {
|
||||||
this.$store.dispatch('user/updateUserSettings', this.settings)
|
this.$store.dispatch('user/updateUserSettings', this.settings)
|
||||||
},
|
},
|
||||||
|
@ -105,6 +105,9 @@ export default {
|
|||||||
filterBy() {
|
filterBy() {
|
||||||
return this.$store.getters['user/getUserSetting']('filterBy')
|
return this.$store.getters['user/getUserSetting']('filterBy')
|
||||||
},
|
},
|
||||||
|
collapseSeries() {
|
||||||
|
return this.$store.getters['user/getUserSetting']('collapseSeries')
|
||||||
|
},
|
||||||
coverAspectRatio() {
|
coverAspectRatio() {
|
||||||
return this.$store.getters['getServerSetting']('coverAspectRatio')
|
return this.$store.getters['getServerSetting']('coverAspectRatio')
|
||||||
},
|
},
|
||||||
@ -207,19 +210,6 @@ export default {
|
|||||||
this.isSelectionMode = false
|
this.isSelectionMode = false
|
||||||
this.isSelectAll = false
|
this.isSelectAll = false
|
||||||
},
|
},
|
||||||
selectAllEntities() {
|
|
||||||
this.isSelectAll = true
|
|
||||||
if (this.entityName === 'books' || this.entityName === 'series-books') {
|
|
||||||
var allAvailableEntityIds = this.entities.map((ent) => ent.id).filter((ent) => !!ent)
|
|
||||||
this.$store.commit('setSelectedAudiobooks', allAvailableEntityIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in this.entityComponentRefs) {
|
|
||||||
if (this.entityIndexesMounted.includes(Number(key))) {
|
|
||||||
this.entityComponentRefs[key].selected = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectEntity(entity) {
|
selectEntity(entity) {
|
||||||
if (this.entityName === 'books' || this.entityName === 'series-books') {
|
if (this.entityName === 'books' || this.entityName === 'series-books') {
|
||||||
this.$store.commit('toggleAudiobookSelected', entity.id)
|
this.$store.commit('toggleAudiobookSelected', entity.id)
|
||||||
@ -255,6 +245,7 @@ export default {
|
|||||||
console.error('failed to fetch books', error)
|
console.error('failed to fetch books', error)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
console.log('payload', payload)
|
||||||
this.isFetchingEntities = false
|
this.isFetchingEntities = false
|
||||||
if (this.pendingReset) {
|
if (this.pendingReset) {
|
||||||
this.pendingReset = false
|
this.pendingReset = false
|
||||||
@ -392,6 +383,9 @@ export default {
|
|||||||
searchParams.set('sort', this.orderBy)
|
searchParams.set('sort', this.orderBy)
|
||||||
searchParams.set('desc', this.orderDesc ? 1 : 0)
|
searchParams.set('desc', this.orderDesc ? 1 : 0)
|
||||||
}
|
}
|
||||||
|
if (this.collapseSeries) {
|
||||||
|
searchParams.set('collapseseries', 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return searchParams.toString()
|
return searchParams.toString()
|
||||||
},
|
},
|
||||||
@ -524,7 +518,6 @@ export default {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.$eventBus.$on('bookshelf-clear-selection', this.clearSelectedEntities)
|
this.$eventBus.$on('bookshelf-clear-selection', this.clearSelectedEntities)
|
||||||
this.$eventBus.$on('bookshelf-select-all', this.selectAllEntities)
|
|
||||||
this.$eventBus.$on('socket_init', this.socketInit)
|
this.$eventBus.$on('socket_init', this.socketInit)
|
||||||
|
|
||||||
this.$store.commit('user/addSettingsListener', { id: 'lazy-bookshelf', meth: this.settingsUpdated })
|
this.$store.commit('user/addSettingsListener', { id: 'lazy-bookshelf', meth: this.settingsUpdated })
|
||||||
@ -546,7 +539,6 @@ export default {
|
|||||||
bookshelf.removeEventListener('scroll', this.scroll)
|
bookshelf.removeEventListener('scroll', this.scroll)
|
||||||
}
|
}
|
||||||
this.$eventBus.$off('bookshelf-clear-selection', this.clearSelectedEntities)
|
this.$eventBus.$off('bookshelf-clear-selection', this.clearSelectedEntities)
|
||||||
this.$eventBus.$off('bookshelf-select-all', this.selectAllEntities)
|
|
||||||
this.$eventBus.$off('socket_init', this.socketInit)
|
this.$eventBus.$off('socket_init', this.socketInit)
|
||||||
|
|
||||||
this.$store.commit('user/removeSettingsListener', 'lazy-bookshelf')
|
this.$store.commit('user/removeSettingsListener', 'lazy-bookshelf')
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ authorFL }}</p>
|
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ authorFL }}</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>
|
||||||
|
|
||||||
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
|
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
|
||||||
<div v-show="audiobook && !imageReady" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: sizeMultiplier * 0.5 + 'rem' }">
|
<div v-show="audiobook && !imageReady" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: sizeMultiplier * 0.5 + 'rem' }">
|
||||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }" class="font-book text-gray-300 text-center">{{ title }}</p>
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }" class="font-book text-gray-300 text-center">{{ title }}</p>
|
||||||
@ -113,6 +115,9 @@ export default {
|
|||||||
_audiobook() {
|
_audiobook() {
|
||||||
return this.audiobook || {}
|
return this.audiobook || {}
|
||||||
},
|
},
|
||||||
|
book() {
|
||||||
|
return this._audiobook.book || {}
|
||||||
|
},
|
||||||
placeholderUrl() {
|
placeholderUrl() {
|
||||||
return '/book_placeholder.jpg'
|
return '/book_placeholder.jpg'
|
||||||
},
|
},
|
||||||
@ -122,6 +127,12 @@ export default {
|
|||||||
audiobookId() {
|
audiobookId() {
|
||||||
return this._audiobook.id
|
return this._audiobook.id
|
||||||
},
|
},
|
||||||
|
series() {
|
||||||
|
return this.book.series
|
||||||
|
},
|
||||||
|
libraryId() {
|
||||||
|
return this._audiobook.libraryId
|
||||||
|
},
|
||||||
hasEbook() {
|
hasEbook() {
|
||||||
return this._audiobook.numEbooks
|
return this._audiobook.numEbooks
|
||||||
},
|
},
|
||||||
@ -131,8 +142,9 @@ export default {
|
|||||||
processingBatch() {
|
processingBatch() {
|
||||||
return this.store.state.processingBatch
|
return this.store.state.processingBatch
|
||||||
},
|
},
|
||||||
book() {
|
booksInSeries() {
|
||||||
return this._audiobook.book || {}
|
// Only added to audiobook object when collapseSeries is enabled
|
||||||
|
return this._audiobook.booksInSeries
|
||||||
},
|
},
|
||||||
hasCover() {
|
hasCover() {
|
||||||
return !!this.book.cover
|
return !!this.book.cover
|
||||||
@ -321,7 +333,10 @@ export default {
|
|||||||
this.selectBtnClick()
|
this.selectBtnClick()
|
||||||
} else {
|
} else {
|
||||||
var router = this.$router || this.$nuxt.$router
|
var router = this.$router || this.$nuxt.$router
|
||||||
if (router) router.push(`/audiobook/${this.audiobookId}`)
|
if (router) {
|
||||||
|
if (this.booksInSeries) router.push(`/library/${this.libraryId}/series/${this.$encode(this.series)}`)
|
||||||
|
else router.push(`/audiobook/${this.audiobookId}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
editClick() {
|
editClick() {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<label class="flex justify-start items-start">
|
<label class="flex justify-start items-center cursor-pointer">
|
||||||
<div class="bg-white border-2 rounded border-gray-400 flex flex-shrink-0 justify-center items-center focus-within:border-blue-500" :class="wrapperClass">
|
<div class="border-2 rounded border-gray-400 flex flex-shrink-0 justify-center items-center" :class="wrapperClass">
|
||||||
<input v-model="selected" type="checkbox" class="opacity-0 absolute" />
|
<input v-model="selected" type="checkbox" class="opacity-0 absolute cursor-pointer" />
|
||||||
<svg v-if="selected" class="fill-current text-green-500 pointer-events-none" :class="svgClass" viewBox="0 0 20 20"><path d="M0 11l2-2 5 5L18 3l2 2L7 18z" /></svg>
|
<svg v-if="selected" class="fill-current pointer-events-none" :class="svgClass" viewBox="0 0 20 20"><path d="M0 11l2-2 5 5L18 3l2 2L7 18z" /></svg>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="label" class="select-none">{{ label }}</div>
|
<div v-if="label" class="select-none pl-1 text-gray-100" :class="labelClass">{{ label }}</div>
|
||||||
</label>
|
</label>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -12,8 +12,16 @@
|
|||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
value: Boolean,
|
value: Boolean,
|
||||||
label: Boolean,
|
label: String,
|
||||||
small: Boolean
|
small: Boolean,
|
||||||
|
checkboxBg: {
|
||||||
|
type: String,
|
||||||
|
default: 'white'
|
||||||
|
},
|
||||||
|
checkColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'green-500'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
@ -28,12 +36,22 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
wrapperClass() {
|
wrapperClass() {
|
||||||
if (this.small) return 'w-4 h-4'
|
var classes = [`bg-${this.checkboxBg}`]
|
||||||
return 'w-6 h-6'
|
if (this.small) classes.push('w-4 h-4')
|
||||||
|
else classes.push('w-6 h-6')
|
||||||
|
|
||||||
|
return classes.join(' ')
|
||||||
|
},
|
||||||
|
labelClass() {
|
||||||
|
if (this.small) return 'text-sm'
|
||||||
|
return ''
|
||||||
},
|
},
|
||||||
svgClass() {
|
svgClass() {
|
||||||
if (this.small) return 'w-3 h-3'
|
var classes = [`text-${this.checkColor}`]
|
||||||
return 'w-4 h-4'
|
if (this.small) classes.push('w-3 h-3')
|
||||||
|
else classes.push('w-4 h-4')
|
||||||
|
|
||||||
|
return classes.join(' ')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {},
|
methods: {},
|
||||||
|
@ -8,7 +8,8 @@ export const state = () => ({
|
|||||||
orderDesc: false,
|
orderDesc: false,
|
||||||
filterBy: 'all',
|
filterBy: 'all',
|
||||||
playbackRate: 1,
|
playbackRate: 1,
|
||||||
bookshelfCoverSize: 120
|
bookshelfCoverSize: 120,
|
||||||
|
collapseSeries: false
|
||||||
},
|
},
|
||||||
settingsListeners: [],
|
settingsListeners: [],
|
||||||
collections: [],
|
collections: [],
|
||||||
|
@ -98,7 +98,6 @@ class LibraryController {
|
|||||||
audiobooks = libraryHelpers.getFiltered(audiobooks, req.query.filter, req.user)
|
audiobooks = libraryHelpers.getFiltered(audiobooks, req.query.filter, req.user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (req.query.sort) {
|
if (req.query.sort) {
|
||||||
var orderByNumber = req.query.sort === 'book.volumeNumber'
|
var orderByNumber = req.query.sort === 'book.volumeNumber'
|
||||||
var direction = req.query.desc === '1' ? 'desc' : 'asc'
|
var direction = req.query.desc === '1' ? 'desc' : 'asc'
|
||||||
@ -120,6 +119,7 @@ class LibraryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// api/libraries/:id/books/all
|
// api/libraries/:id/books/all
|
||||||
|
// TODO: Optimize this method, audiobooks are iterated through several times but can be combined
|
||||||
getBooksForLibrary2(req, res) {
|
getBooksForLibrary2(req, res) {
|
||||||
var libraryId = req.library.id
|
var libraryId = req.library.id
|
||||||
|
|
||||||
@ -132,7 +132,8 @@ class LibraryController {
|
|||||||
sortBy: req.query.sort,
|
sortBy: req.query.sort,
|
||||||
sortDesc: req.query.desc === '1',
|
sortDesc: req.query.desc === '1',
|
||||||
filterBy: req.query.filter,
|
filterBy: req.query.filter,
|
||||||
minified: req.query.minified === '1'
|
minified: req.query.minified === '1',
|
||||||
|
collapseseries: req.query.collapseseries === '1'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.filterBy) {
|
if (payload.filterBy) {
|
||||||
@ -148,11 +149,38 @@ class LibraryController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payload.collapseseries) {
|
||||||
|
var series = {}
|
||||||
|
// Group abs by series
|
||||||
|
for (let i = 0; i < audiobooks.length; i++) {
|
||||||
|
var ab = audiobooks[i]
|
||||||
|
if (ab.book.series) {
|
||||||
|
if (!series[ab.book.series]) series[ab.book.series] = []
|
||||||
|
series[ab.book.series].push(ab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort series by volume number and filter out all but the first book in series
|
||||||
|
var seriesBooksToKeep = Object.values(series).map((_series) => {
|
||||||
|
var sorted = naturalSort(_series).asc(_ab => _ab.book.volumeNumber)
|
||||||
|
return sorted[0].id
|
||||||
|
})
|
||||||
|
// Add "booksInSeries" field to audiobook payload
|
||||||
|
audiobooks = audiobooks.filter(ab => !ab.book.series || seriesBooksToKeep.includes(ab.id)).map(ab => {
|
||||||
|
var abJson = payload.minified ? ab.toJSONMinified() : ab.toJSONExpanded()
|
||||||
|
if (ab.book.series) abJson.booksInSeries = series[ab.book.series].length
|
||||||
|
return abJson
|
||||||
|
})
|
||||||
|
payload.total = audiobooks.length
|
||||||
|
} else {
|
||||||
|
audiobooks = audiobooks.map(ab => payload.minified ? ab.toJSONMinified() : ab.toJSONExpanded())
|
||||||
|
}
|
||||||
|
|
||||||
if (payload.limit) {
|
if (payload.limit) {
|
||||||
var startIndex = payload.page * payload.limit
|
var startIndex = payload.page * payload.limit
|
||||||
audiobooks = audiobooks.slice(startIndex, startIndex + payload.limit)
|
audiobooks = audiobooks.slice(startIndex, startIndex + payload.limit)
|
||||||
}
|
}
|
||||||
payload.results = audiobooks.map(ab => payload.minified ? ab.toJSONMinified() : ab.toJSONExpanded())
|
payload.results = audiobooks
|
||||||
res.json(payload)
|
res.json(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,8 @@ class User {
|
|||||||
orderDesc: false,
|
orderDesc: false,
|
||||||
filterBy: 'all',
|
filterBy: 'all',
|
||||||
playbackRate: 1,
|
playbackRate: 1,
|
||||||
bookshelfCoverSize: 120
|
bookshelfCoverSize: 120,
|
||||||
|
collapseSeries: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user