diff --git a/client/components/app/LazyBookshelf.vue b/client/components/app/LazyBookshelf.vue index d27ecdca..29b06df3 100644 --- a/client/components/app/LazyBookshelf.vue +++ b/client/components/app/LazyBookshelf.vue @@ -2,6 +2,10 @@
@@ -65,7 +69,13 @@ export default { tempIsScanning: false, cardWidth: 0, cardHeight: 0, - resizeObserver: null + coverHeight: 0, + resizeObserver: null, + lastScrollTop: 0, + lastTimestamp: 0, + postScrollTimeout: null, + currFirstEntityIndex: -1, + currLastEntityIndex: -1 } }, watch: { @@ -171,9 +181,6 @@ export default { bookWidth() { return this.cardWidth }, - bookHeight() { - return this.cardHeight - }, shelfPadding() { if (this.bookshelfWidth < 640) return 32 * this.sizeMultiplier return 64 * this.sizeMultiplier @@ -184,9 +191,6 @@ export default { entityWidth() { return this.cardWidth }, - entityHeight() { - return this.cardHeight - }, shelfPaddingHeight() { return 16 }, @@ -354,50 +358,53 @@ export default { } }, loadPage(page) { - this.pagesLoaded[page] = true - this.fetchEntites(page) + if (!this.pagesLoaded[page]) this.pagesLoaded[page] = this.fetchEntites(page) + return this.pagesLoaded[page] }, showHideBookPlaceholder(index, show) { var el = document.getElementById(`book-${index}-placeholder`) if (el) el.style.display = show ? 'flex' : 'none' }, - mountEntites(fromIndex, toIndex) { + mountEntities(fromIndex, toIndex) { for (let i = fromIndex; i < toIndex; i++) { if (!this.entityIndexesMounted.includes(i)) { this.cardsHelpers.mountEntityCard(i) } } }, - handleScroll(scrollTop) { - this.currScrollTop = scrollTop - var firstShelfIndex = Math.floor(scrollTop / this.shelfHeight) - var lastShelfIndex = Math.ceil((scrollTop + this.bookshelfHeight) / this.shelfHeight) - lastShelfIndex = Math.min(this.totalShelves - 1, lastShelfIndex) - - 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) - } - if (!this.pagesLoaded[lastBookPage]) { - // console.log('Must load last next batch', lastBookPage, 'book index', lastBookIndex) - this.loadPage(lastBookPage) - } - + getVisibleIndices(scrollTop) { + const firstShelfIndex = Math.floor(scrollTop / this.shelfHeight) + const lastShelfIndex = Math.min(Math.ceil((scrollTop + this.bookshelfHeight) / this.shelfHeight), this.totalShelves - 1) + const firstEntityIndex = firstShelfIndex * this.entitiesPerShelf + const lastEntityIndex = Math.min(lastShelfIndex * this.entitiesPerShelf + this.entitiesPerShelf, this.totalEntities) + return { firstEntityIndex, lastEntityIndex } + }, + postScroll() { + const { firstEntityIndex, lastEntityIndex } = this.getVisibleIndices(this.currScrollTop) this.entityIndexesMounted = this.entityIndexesMounted.filter((_index) => { - if (_index < firstBookIndex || _index >= lastBookIndex) { - var el = document.getElementById(`book-card-${_index}`) - if (el) el.remove() + if (_index < firstEntityIndex || _index >= lastEntityIndex) { + var el = this.entityComponentRefs[_index] + if (el && el.$el) el.$el.remove() return false } return true }) - this.mountEntites(firstBookIndex, lastBookIndex) + }, + handleScroll(scrollTop) { + this.currScrollTop = scrollTop + const { firstEntityIndex, lastEntityIndex } = this.getVisibleIndices(scrollTop) + if (firstEntityIndex === this.currFirstEntityIndex && lastEntityIndex === this.currLastEntityIndex) return + this.currFirstEntityIndex = firstEntityIndex + this.currLastEntityIndex = lastEntityIndex + + clearTimeout(this.postScrollTimeout) + const firstPage = Math.floor(firstEntityIndex / this.booksPerFetch) + const lastPage = Math.floor(lastEntityIndex / this.booksPerFetch) + Promise.all([this.loadPage(firstPage), this.loadPage(lastPage)]) + .then(() => this.mountEntities(firstEntityIndex, lastEntityIndex)) + .catch((error) => console.error('Failed to load page', error)) + + this.postScrollTimeout = setTimeout(this.postScroll, 500) }, async resetEntities() { if (this.isFetchingEntities) { @@ -405,8 +412,6 @@ export default { return } this.destroyEntityComponents() - this.entityIndexesMounted = [] - this.entityComponentRefs = {} this.pagesLoaded = {} this.entities = [] this.totalShelves = 0 @@ -416,40 +421,21 @@ export default { this.initialized = false this.initSizeData() - this.pagesLoaded[0] = true - await this.fetchEntites(0) + await this.loadPage(0) var lastBookIndex = Math.min(this.totalEntities, this.shelvesPerPage * this.entitiesPerShelf) - this.mountEntites(0, lastBookIndex) + this.mountEntities(0, lastBookIndex) }, - remountEntities() { - for (const key in this.entityComponentRefs) { - if (this.entityComponentRefs[key]) { - this.entityComponentRefs[key].destroy() - } - } - this.entityComponentRefs = {} - this.entityIndexesMounted.forEach((i) => { - this.cardsHelpers.mountEntityCard(i) - }) - }, - rebuild() { + async rebuild() { this.initSizeData() var lastBookIndex = Math.min(this.totalEntities, this.booksPerFetch) - this.entityIndexesMounted = [] - for (let i = 0; i < lastBookIndex; i++) { - this.entityIndexesMounted.push(i) - if (!this.entities[i]) { - const page = Math.floor(i / this.booksPerFetch) - this.loadPage(page) - } - } + this.destroyEntityComponents() + await this.loadPage(0) var bookshelfEl = document.getElementById('bookshelf') if (bookshelfEl) { bookshelfEl.scrollTop = 0 } - - this.$nextTick(this.remountEntities) + this.mountEntities(0, lastBookIndex) }, buildSearchParams() { if (this.page === 'search' || this.page === 'collections') { @@ -513,12 +499,29 @@ export default { if (wasUpdated) { this.resetEntities() } else if (settings.bookshelfCoverSize !== this.currentBookWidth) { - this.executeRebuild() + this.rebuild() } }, + getScrollRate() { + const currentTimestamp = Date.now() + const timeDelta = currentTimestamp - this.lastTimestamp + const scrollDelta = this.currScrollTop - this.lastScrollTop + const scrollRate = Math.abs(scrollDelta) / (timeDelta || 1) + this.lastScrollTop = this.currScrollTop + this.lastTimestamp = currentTimestamp + return scrollRate + }, scroll(e) { if (!e || !e.target) return - var { scrollTop } = e.target + clearTimeout(this.scrollTimeout) + const { scrollTop } = e.target + const scrollRate = this.getScrollRate() + if (scrollRate > 5) { + this.scrollTimeout = setTimeout(() => { + this.handleScroll(scrollTop) + }, 25) + return + } this.handleScroll(scrollTop) }, libraryItemAdded(libraryItem) { @@ -667,13 +670,14 @@ export default { }, updatePagesLoaded() { let numPages = Math.ceil(this.totalEntities / this.booksPerFetch) + this.pagesLoaded = {} for (let page = 0; page < numPages; page++) { let numEntities = Math.min(this.totalEntities - page * this.booksPerFetch, this.booksPerFetch) - this.pagesLoaded[page] = true + this.pagesLoaded[page] = Promise.resolve() for (let i = 0; i < numEntities; i++) { const index = page * this.booksPerFetch + i if (!this.entities[index]) { - this.pagesLoaded[page] = false + if (this.pagesLoaded[page]) delete this.pagesLoaded[page] break } } @@ -688,7 +692,6 @@ export default { var entitiesPerShelfBefore = this.entitiesPerShelf var { clientHeight, clientWidth } = bookshelf - // console.log('Init bookshelf width', clientWidth, 'window width', window.innerWidth) this.mountWindowWidth = window.innerWidth this.bookshelfHeight = clientHeight this.bookshelfWidth = clientWidth @@ -713,10 +716,9 @@ export default { this.initSizeData(bookshelf) this.checkUpdateSearchParams() - this.pagesLoaded[0] = true - await this.fetchEntites(0) + await this.loadPage(0) var lastBookIndex = Math.min(this.totalEntities, this.shelvesPerPage * this.entitiesPerShelf) - this.mountEntites(0, lastBookIndex) + this.mountEntities(0, lastBookIndex) // Set last scroll position for this bookshelf page if (this.$store.state.lastBookshelfScrollData[this.page] && window.bookshelf) { @@ -747,7 +749,7 @@ export default { var bookshelf = document.getElementById('bookshelf') if (bookshelf) { this.init(bookshelf) - bookshelf.addEventListener('scroll', this.scroll) + bookshelf.addEventListener('scroll', this.scroll, { passive: true }) } }) @@ -810,10 +812,14 @@ export default { }, destroyEntityComponents() { for (const key in this.entityComponentRefs) { - if (this.entityComponentRefs[key] && this.entityComponentRefs[key].destroy) { - this.entityComponentRefs[key].destroy() + const ref = this.entityComponentRefs[key] + if (ref && ref.destroy) { + if (ref.$el) ref.$el.remove() + ref.destroy() } } + this.entityComponentRefs = {} + this.entityIndexesMounted = [] }, scan() { this.tempIsScanning = true @@ -826,6 +832,14 @@ export default { .finally(() => { this.tempIsScanning = false }) + }, + entitiesInShelf(shelf) { + return shelf == this.totalShelves ? this.totalEntities % this.entitiesPerShelf || this.entitiesPerShelf : this.entitiesPerShelf + }, + entityTransform(entityIndex) { + const shelfOffsetY = this.shelfPaddingHeight * this.sizeMultiplier + const shelfOffsetX = (entityIndex - 1) * this.totalEntityCardWidth + this.bookshelfMarginLeft + return `translate3d(${shelfOffsetX}px, ${shelfOffsetY}px, 0px)` } }, async mounted() { diff --git a/client/components/cards/AuthorCard.vue b/client/components/cards/AuthorCard.vue index 1dafa83b..7ffcde73 100644 --- a/client/components/cards/AuthorCard.vue +++ b/client/components/cards/AuthorCard.vue @@ -68,6 +68,9 @@ export default { cardHeight() { return this.height * this.sizeMultiplier }, + coverHeight() { + return this.cardHeight + }, userToken() { return this.store.getters['user/getToken'] }, diff --git a/client/components/cards/LazyBookCard.vue b/client/components/cards/LazyBookCard.vue index cffa45a5..1791971e 100644 --- a/client/components/cards/LazyBookCard.vue +++ b/client/components/cards/LazyBookCard.vue @@ -19,7 +19,7 @@
- +
diff --git a/client/mixins/bookshelfCardsHelpers.js b/client/mixins/bookshelfCardsHelpers.js index 802b2cc8..d3e18128 100644 --- a/client/mixins/bookshelfCardsHelpers.js +++ b/client/mixins/bookshelfCardsHelpers.js @@ -57,9 +57,10 @@ export default { for (let entry of entries) { this.cardWidth = entry.borderBoxSize[0].inlineSize this.cardHeight = entry.borderBoxSize[0].blockSize - this.resizeObserver.disconnect() - this.$refs.bookshelf.removeChild(instance.$el) } + this.coverHeight = instance.coverHeight + this.resizeObserver.disconnect() + this.$refs.bookshelf.removeChild(instance.$el) }) instance.$el.style.visibility = 'hidden' instance.$el.style.position = 'absolute' @@ -131,10 +132,7 @@ export default { this.entityComponentRefs[index] = instance instance.$mount() - const shelfOffsetY = this.shelfPaddingHeight * this.sizeMultiplier - const row = index % this.entitiesPerShelf - const shelfOffsetX = row * this.totalEntityCardWidth + this.bookshelfMarginLeft - instance.$el.style.transform = `translate3d(${shelfOffsetX}px, ${shelfOffsetY}px, 0px)` + instance.$el.style.transform = this.entityTransform((index % this.entitiesPerShelf) + 1) instance.$el.classList.add('absolute', 'top-0', 'left-0') shelfEl.appendChild(instance.$el)