2021-11-29 02:36:44 +01:00
|
|
|
<template>
|
|
|
|
<div id="bookshelf" class="w-full overflow-y-auto">
|
|
|
|
<template v-for="shelf in totalShelves">
|
|
|
|
<div :key="shelf" class="w-full px-8 bookshelfRow relative" :id="`shelf-${shelf - 1}`" :style="{ height: shelfHeight + 'px' }">
|
2021-12-01 03:02:40 +01:00
|
|
|
<!-- <div class="absolute top-0 left-0 bottom-0 p-4 z-10">
|
2021-11-29 02:36:44 +01:00
|
|
|
<p class="text-white text-2xl">{{ shelf }}</p>
|
2021-12-01 03:02:40 +01:00
|
|
|
</div> -->
|
2021-11-29 02:36:44 +01:00
|
|
|
<div class="bookshelfDivider w-full absolute bottom-0 left-0 right-0 z-10" :class="`h-${shelfDividerHeightIndex}`" />
|
|
|
|
</div>
|
|
|
|
</template>
|
2021-12-01 03:02:40 +01:00
|
|
|
|
|
|
|
<div v-if="!totalShelves && initialized" class="w-full py-16">
|
|
|
|
<p class="text-xl text-center">{{ emptyMessage }}</p>
|
|
|
|
</div>
|
2021-11-29 02:36:44 +01:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
2021-12-01 03:02:40 +01:00
|
|
|
import bookshelfCardsHelpers from '@/mixins/bookshelfCardsHelpers'
|
2021-11-29 02:36:44 +01:00
|
|
|
|
|
|
|
export default {
|
2021-12-01 03:02:40 +01:00
|
|
|
props: {
|
|
|
|
page: String
|
|
|
|
},
|
|
|
|
mixins: [bookshelfCardsHelpers],
|
2021-11-29 02:36:44 +01:00
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
initialized: false,
|
|
|
|
bookshelfHeight: 0,
|
|
|
|
bookshelfWidth: 0,
|
|
|
|
shelvesPerPage: 0,
|
2021-12-01 03:02:40 +01:00
|
|
|
entitiesPerShelf: 8,
|
2021-11-29 02:36:44 +01:00
|
|
|
currentPage: 0,
|
2021-12-01 03:02:40 +01:00
|
|
|
totalEntities: 0,
|
|
|
|
entities: [],
|
2021-11-29 02:36:44 +01:00
|
|
|
pagesLoaded: {},
|
2021-12-01 03:02:40 +01:00
|
|
|
entityIndexesMounted: [],
|
|
|
|
entityComponentRefs: {},
|
2021-11-29 02:36:44 +01:00
|
|
|
bookWidth: 120,
|
|
|
|
pageLoadQueue: [],
|
2021-12-01 03:02:40 +01:00
|
|
|
isFetchingEntities: false,
|
2021-11-29 02:36:44 +01:00
|
|
|
scrollTimeout: null,
|
2021-12-01 03:02:40 +01:00
|
|
|
booksPerFetch: 250,
|
2021-11-29 02:36:44 +01:00
|
|
|
totalShelves: 0,
|
2021-12-01 03:02:40 +01:00
|
|
|
bookshelfMarginLeft: 0,
|
|
|
|
isSelectionMode: false,
|
|
|
|
isSelectAll: false,
|
|
|
|
currentSFQueryString: null,
|
|
|
|
pendingReset: false,
|
|
|
|
keywordFilter: null
|
2021-11-29 02:36:44 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
2021-12-01 03:02:40 +01:00
|
|
|
// booksFiltered() {
|
|
|
|
// const keywordFilterKeys = ['title', 'subtitle', 'author', 'series', 'narrator']
|
|
|
|
// const keyworkFilter = state.keywordFilter.toLowerCase()
|
|
|
|
// return this.books.filter((ab) => {
|
|
|
|
// if (!ab.book) return false
|
|
|
|
// return !!keywordFilterKeys.find((key) => ab.book[key] && ab.book[key].toLowerCase().includes(keyworkFilter))
|
|
|
|
// })
|
|
|
|
// },
|
|
|
|
emptyMessage() {
|
|
|
|
if (this.page === 'series') return `You have no series`
|
|
|
|
if (this.page === 'collections') return "You haven't made any collections yet"
|
|
|
|
return 'No results'
|
|
|
|
},
|
|
|
|
entityName() {
|
|
|
|
if (this.page === 'series') return 'series'
|
|
|
|
if (this.page === 'collections') return 'collections'
|
|
|
|
return 'books'
|
|
|
|
},
|
|
|
|
orderBy() {
|
2021-11-29 02:36:44 +01:00
|
|
|
return this.$store.getters['user/getUserSetting']('orderBy')
|
|
|
|
},
|
2021-12-01 03:02:40 +01:00
|
|
|
orderDesc() {
|
2021-11-29 02:36:44 +01:00
|
|
|
return this.$store.getters['user/getUserSetting']('orderDesc')
|
|
|
|
},
|
|
|
|
filterBy() {
|
|
|
|
return this.$store.getters['user/getUserSetting']('filterBy')
|
|
|
|
},
|
|
|
|
currentLibraryId() {
|
|
|
|
return this.$store.state.libraries.currentLibraryId
|
|
|
|
},
|
2021-12-01 03:02:40 +01:00
|
|
|
entityWidth() {
|
|
|
|
if (this.entityName === 'series') return this.bookWidth * 1.6
|
|
|
|
if (this.entityName === 'collections') return this.bookWidth * 2
|
|
|
|
return this.bookWidth
|
|
|
|
},
|
2021-11-29 02:36:44 +01:00
|
|
|
bookHeight() {
|
|
|
|
return this.bookWidth * 1.6
|
|
|
|
},
|
|
|
|
shelfDividerHeightIndex() {
|
|
|
|
return 6
|
|
|
|
},
|
|
|
|
shelfHeight() {
|
|
|
|
return this.bookHeight + 40
|
|
|
|
},
|
2021-12-01 03:02:40 +01:00
|
|
|
totalEntityCardWidth() {
|
2021-11-29 02:36:44 +01:00
|
|
|
// Includes margin
|
2021-12-01 03:02:40 +01:00
|
|
|
return this.entityWidth + 24
|
2021-11-29 02:36:44 +01:00
|
|
|
},
|
|
|
|
booksPerPage() {
|
2021-12-01 03:02:40 +01:00
|
|
|
return this.shelvesPerPage * this.entitiesPerShelf
|
|
|
|
},
|
|
|
|
selectedAudiobooks() {
|
|
|
|
return this.$store.state.selectedAudiobooks || []
|
2021-11-29 02:36:44 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
methods: {
|
2021-12-01 03:02:40 +01:00
|
|
|
editEntity(entity) {
|
|
|
|
if (this.entityName === 'books') {
|
|
|
|
var bookIds = this.entities.map((e) => e.id)
|
|
|
|
this.$store.commit('setBookshelfBookIds', bookIds)
|
|
|
|
this.$store.commit('showEditModal', entity)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
clearSelectedBooks() {
|
|
|
|
this.updateBookSelectionMode(false)
|
|
|
|
this.isSelectionMode = false
|
|
|
|
this.isSelectAll = false
|
|
|
|
},
|
|
|
|
selectAllBooks() {
|
|
|
|
this.isSelectAll = true
|
|
|
|
for (const key in this.entityComponentRefs) {
|
|
|
|
if (this.entityIndexesMounted.includes(Number(key))) {
|
|
|
|
this.entityComponentRefs[key].selected = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
selectEntity(entity) {
|
|
|
|
if (this.entityName === 'books') {
|
|
|
|
this.$store.commit('toggleAudiobookSelected', entity.id)
|
|
|
|
|
|
|
|
var newIsSelectionMode = !!this.selectedAudiobooks.length
|
|
|
|
if (this.isSelectionMode !== newIsSelectionMode) {
|
|
|
|
this.isSelectionMode = newIsSelectionMode
|
|
|
|
this.updateBookSelectionMode(newIsSelectionMode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
updateBookSelectionMode(isSelectionMode) {
|
|
|
|
for (const key in this.entityComponentRefs) {
|
|
|
|
if (this.entityIndexesMounted.includes(Number(key))) {
|
|
|
|
this.entityComponentRefs[key].setSelectionMode(isSelectionMode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async fetchEntites(page = 0) {
|
2021-11-29 02:36:44 +01:00
|
|
|
var startIndex = page * this.booksPerFetch
|
|
|
|
|
2021-12-01 03:02:40 +01:00
|
|
|
this.isFetchingEntities = true
|
|
|
|
|
|
|
|
if (!this.initialized) {
|
|
|
|
this.currentSFQueryString = this.buildSearchParams()
|
|
|
|
}
|
|
|
|
|
|
|
|
var entityPath = this.entityName === 'books' ? `books/all` : this.entityName
|
|
|
|
var sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : ''
|
|
|
|
var payload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/${entityPath}?${sfQueryString}limit=${this.booksPerFetch}&page=${page}`).catch((error) => {
|
2021-11-29 02:36:44 +01:00
|
|
|
console.error('failed to fetch books', error)
|
|
|
|
return null
|
|
|
|
})
|
2021-12-01 03:02:40 +01:00
|
|
|
this.isFetchingEntities = false
|
|
|
|
if (this.pendingReset) {
|
|
|
|
this.pendingReset = false
|
|
|
|
this.resetEntities()
|
|
|
|
return
|
|
|
|
}
|
2021-11-29 02:36:44 +01:00
|
|
|
if (payload) {
|
2021-12-01 03:02:40 +01:00
|
|
|
console.log('Received payload', payload)
|
2021-11-29 02:36:44 +01:00
|
|
|
if (!this.initialized) {
|
|
|
|
this.initialized = true
|
2021-12-01 03:02:40 +01:00
|
|
|
this.totalEntities = payload.total
|
|
|
|
this.totalShelves = Math.ceil(this.totalEntities / this.entitiesPerShelf)
|
|
|
|
this.entities = new Array(this.totalEntities)
|
|
|
|
this.$eventBus.$emit('bookshelf-total-entities', this.totalEntities)
|
2021-11-29 02:36:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < payload.results.length; i++) {
|
2021-12-01 03:02:40 +01:00
|
|
|
var index = i + startIndex
|
|
|
|
this.entities[index] = payload.results[i]
|
2021-11-29 02:36:44 +01:00
|
|
|
|
2021-12-01 03:02:40 +01:00
|
|
|
if (this.entityComponentRefs[index]) {
|
|
|
|
this.entityComponentRefs[index].setEntity(this.entities[index])
|
2021-11-29 02:36:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
loadPage(page) {
|
|
|
|
this.pagesLoaded[page] = true
|
2021-12-01 03:02:40 +01:00
|
|
|
this.fetchEntites(page)
|
2021-11-29 02:36:44 +01:00
|
|
|
},
|
|
|
|
showHideBookPlaceholder(index, show) {
|
|
|
|
var el = document.getElementById(`book-${index}-placeholder`)
|
|
|
|
if (el) el.style.display = show ? 'flex' : 'none'
|
|
|
|
},
|
2021-12-01 03:02:40 +01:00
|
|
|
mountEntites(fromIndex, toIndex) {
|
2021-11-29 02:36:44 +01:00
|
|
|
for (let i = fromIndex; i < toIndex; i++) {
|
2021-12-01 03:02:40 +01:00
|
|
|
if (!this.entityIndexesMounted.includes(i)) {
|
|
|
|
this.cardsHelpers.mountEntityCard(i)
|
|
|
|
}
|
2021-11-29 02:36:44 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
handleScroll(scrollTop) {
|
|
|
|
var firstShelfIndex = Math.floor(scrollTop / this.shelfHeight)
|
|
|
|
var lastShelfIndex = Math.ceil((scrollTop + this.bookshelfHeight) / this.shelfHeight)
|
2021-12-01 03:02:40 +01:00
|
|
|
lastShelfIndex = Math.min(this.totalShelves - 1, lastShelfIndex)
|
2021-11-29 02:36:44 +01:00
|
|
|
|
2021-12-01 03:02:40 +01:00
|
|
|
var firstBookIndex = firstShelfIndex * this.entitiesPerShelf
|
|
|
|
var lastBookIndex = lastShelfIndex * this.entitiesPerShelf + this.entitiesPerShelf
|
|
|
|
lastBookIndex = Math.min(this.totalEntities, lastBookIndex)
|
|
|
|
|
|
|
|
var firstBookPage = Math.floor(firstBookIndex / this.booksPerFetch)
|
|
|
|
var lastBookPage = Math.floor(lastBookIndex / this.booksPerFetch)
|
|
|
|
if (!this.pagesLoaded[firstBookPage]) {
|
|
|
|
console.log('Must load next batch', firstBookPage, 'book index', firstBookIndex)
|
|
|
|
this.loadPage(firstBookPage)
|
2021-11-29 02:36:44 +01:00
|
|
|
}
|
2021-12-01 03:02:40 +01:00
|
|
|
if (!this.pagesLoaded[lastBookPage]) {
|
|
|
|
console.log('Must load last next batch', lastBookPage, 'book index', lastBookIndex)
|
|
|
|
this.loadPage(lastBookPage)
|
2021-11-29 02:36:44 +01:00
|
|
|
}
|
|
|
|
|
2021-12-01 03:02:40 +01:00
|
|
|
this.entityIndexesMounted = this.entityIndexesMounted.filter((_index) => {
|
2021-11-29 02:36:44 +01:00
|
|
|
if (_index < firstBookIndex || _index >= lastBookIndex) {
|
|
|
|
var el = document.getElementById(`book-card-${_index}`)
|
|
|
|
if (el) el.remove()
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
2021-12-01 03:02:40 +01:00
|
|
|
this.mountEntites(firstBookIndex, lastBookIndex)
|
|
|
|
},
|
|
|
|
async resetEntities() {
|
|
|
|
if (this.isFetchingEntities) {
|
|
|
|
console.warn('RESET BOOKS BUT ALREADY FETCHING, WAIT FOR FETCH')
|
|
|
|
this.pendingReset = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.destroyEntityComponents()
|
|
|
|
this.entityIndexesMounted = []
|
|
|
|
this.entityComponentRefs = {}
|
|
|
|
this.pagesLoaded = {}
|
|
|
|
this.entities = []
|
|
|
|
this.totalShelves = 0
|
|
|
|
this.totalEntities = 0
|
|
|
|
this.currentPage = 0
|
|
|
|
this.isSelectionMode = false
|
|
|
|
this.isSelectAll = false
|
|
|
|
this.initialized = false
|
|
|
|
|
|
|
|
this.pagesLoaded[0] = true
|
|
|
|
await this.fetchEntites(0)
|
|
|
|
var lastBookIndex = Math.min(this.totalEntities, this.shelvesPerPage * this.entitiesPerShelf)
|
|
|
|
this.mountEntites(0, lastBookIndex)
|
|
|
|
},
|
|
|
|
buildSearchParams() {
|
|
|
|
if (this.page === 'search' || this.page === 'series' || this.page === 'collections') {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
|
|
|
|
let searchParams = new URLSearchParams()
|
|
|
|
if (this.filterBy && this.filterBy !== 'all') {
|
|
|
|
searchParams.set('filter', this.filterBy)
|
|
|
|
}
|
|
|
|
if (this.orderBy) {
|
|
|
|
searchParams.set('sort', this.orderBy)
|
|
|
|
searchParams.set('desc', this.orderDesc ? 1 : 0)
|
|
|
|
}
|
|
|
|
return searchParams.toString()
|
|
|
|
},
|
|
|
|
checkUpdateSearchParams() {
|
|
|
|
var newSearchParams = this.buildSearchParams()
|
|
|
|
var currentQueryString = window.location.search
|
|
|
|
|
|
|
|
if (newSearchParams === '') {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newSearchParams !== this.currentSFQueryString || newSearchParams !== currentQueryString) {
|
|
|
|
let newurl = window.location.protocol + '//' + window.location.host + window.location.pathname + '?' + newSearchParams
|
|
|
|
window.history.replaceState({ path: newurl }, '', newurl)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
settingsUpdated(settings) {
|
|
|
|
var wasUpdated = this.checkUpdateSearchParams()
|
|
|
|
if (wasUpdated) {
|
|
|
|
this.resetEntities()
|
|
|
|
}
|
2021-11-29 02:36:44 +01:00
|
|
|
},
|
|
|
|
scroll(e) {
|
|
|
|
if (!e || !e.target) return
|
|
|
|
var { scrollTop } = e.target
|
|
|
|
// clearTimeout(this.scrollTimeout)
|
|
|
|
// this.scrollTimeout = setTimeout(() => {
|
|
|
|
this.handleScroll(scrollTop)
|
|
|
|
// }, 250)
|
|
|
|
},
|
|
|
|
async init(bookshelf) {
|
2021-12-01 03:02:40 +01:00
|
|
|
this.checkUpdateSearchParams()
|
|
|
|
|
2021-11-29 02:36:44 +01:00
|
|
|
var { clientHeight, clientWidth } = bookshelf
|
|
|
|
this.bookshelfHeight = clientHeight
|
|
|
|
this.bookshelfWidth = clientWidth
|
2021-12-01 03:02:40 +01:00
|
|
|
this.entitiesPerShelf = Math.floor((this.bookshelfWidth - 64) / this.totalEntityCardWidth)
|
2021-11-29 02:36:44 +01:00
|
|
|
this.shelvesPerPage = Math.ceil(this.bookshelfHeight / this.shelfHeight) + 2
|
2021-12-01 03:02:40 +01:00
|
|
|
this.bookshelfMarginLeft = (this.bookshelfWidth - this.entitiesPerShelf * this.totalEntityCardWidth) / 2
|
2021-11-29 02:36:44 +01:00
|
|
|
|
|
|
|
this.pagesLoaded[0] = true
|
2021-12-01 03:02:40 +01:00
|
|
|
await this.fetchEntites(0)
|
|
|
|
var lastBookIndex = Math.min(this.totalEntities, this.shelvesPerPage * this.entitiesPerShelf)
|
|
|
|
this.mountEntites(0, lastBookIndex)
|
|
|
|
},
|
|
|
|
initListeners() {
|
|
|
|
var bookshelf = document.getElementById('bookshelf')
|
|
|
|
if (bookshelf) {
|
|
|
|
this.init(bookshelf)
|
|
|
|
bookshelf.addEventListener('scroll', this.scroll)
|
|
|
|
}
|
|
|
|
this.$eventBus.$on('bookshelf-clear-selection', this.clearSelectedBooks)
|
|
|
|
this.$eventBus.$on('bookshelf-select-all', this.selectAllBooks)
|
|
|
|
this.$eventBus.$on('bookshelf-keyword-filter', this.updateKeywordFilter)
|
|
|
|
|
|
|
|
this.$store.commit('user/addSettingsListener', { id: 'lazy-bookshelf', meth: this.settingsUpdated })
|
|
|
|
},
|
|
|
|
removeListeners() {
|
|
|
|
var bookshelf = document.getElementById('bookshelf')
|
|
|
|
if (bookshelf) {
|
|
|
|
bookshelf.removeEventListener('scroll', this.scroll)
|
|
|
|
}
|
|
|
|
this.$eventBus.$off('bookshelf-clear-selection', this.clearSelectedBooks)
|
|
|
|
this.$eventBus.$off('bookshelf-select-all', this.selectAllBooks)
|
|
|
|
this.$eventBus.$off('bookshelf-keyword-filter', this.updateKeywordFilter)
|
|
|
|
|
|
|
|
this.$store.commit('user/removeSettingsListener', 'lazy-bookshelf')
|
|
|
|
},
|
|
|
|
destroyEntityComponents() {
|
|
|
|
for (const key in this.entityComponentRefs) {
|
|
|
|
if (this.entityComponentRefs[key] && this.entityComponentRefs[key].destroy) {
|
|
|
|
this.entityComponentRefs[key].destroy()
|
|
|
|
}
|
|
|
|
}
|
2021-11-29 02:36:44 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
mounted() {
|
2021-12-01 03:02:40 +01:00
|
|
|
this.initListeners()
|
2021-11-29 02:36:44 +01:00
|
|
|
},
|
|
|
|
beforeDestroy() {
|
2021-12-01 03:02:40 +01:00
|
|
|
this.destroyEntityComponents()
|
|
|
|
this.removeListeners()
|
2021-11-29 02:36:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style>
|
|
|
|
.bookshelfRow {
|
|
|
|
background-image: var(--bookshelf-texture-img);
|
|
|
|
}
|
|
|
|
.bookshelfDivider {
|
|
|
|
background: rgb(149, 119, 90);
|
|
|
|
background: var(--bookshelf-divider-bg);
|
|
|
|
box-shadow: 2px 14px 8px #111111aa;
|
|
|
|
}
|
|
|
|
</style>
|