From ae9efe63596493be6413650586725b881a24e6d2 Mon Sep 17 00:00:00 2001 From: Greg Lorenzen Date: Thu, 31 Oct 2024 15:30:51 +0000 Subject: [PATCH 01/55] Add keyboard focus to MultiSelectQueryInput edit and close --- client/components/ui/MultiSelectQueryInput.vue | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client/components/ui/MultiSelectQueryInput.vue b/client/components/ui/MultiSelectQueryInput.vue index 099ee709..6b33acf3 100644 --- a/client/components/ui/MultiSelectQueryInput.vue +++ b/client/components/ui/MultiSelectQueryInput.vue @@ -5,9 +5,9 @@
-
- edit - close +
+ edit + close
{{ item[textKey] }}
@@ -65,6 +65,7 @@ export default { currentSearch: null, typingTimeout: null, isFocused: false, + inputFocused: false, menu: null, items: [] } @@ -114,6 +115,9 @@ export default { getIsSelected(itemValue) { return !!this.selected.find((i) => i.id === itemValue) }, + setInputFocused(focused) { + this.inputFocused = focused + }, search() { if (!this.textInput) return this.currentSearch = this.textInput From e55db0afdca634b4a0b9e4fafbb9bdf519c6b029 Mon Sep 17 00:00:00 2001 From: Greg Lorenzen Date: Thu, 31 Oct 2024 15:44:19 +0000 Subject: [PATCH 02/55] Add focus and enter key support to the add button in MultiSelectQueryInput --- client/components/ui/MultiSelectQueryInput.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/ui/MultiSelectQueryInput.vue b/client/components/ui/MultiSelectQueryInput.vue index 6b33acf3..d0bdcef2 100644 --- a/client/components/ui/MultiSelectQueryInput.vue +++ b/client/components/ui/MultiSelectQueryInput.vue @@ -12,7 +12,7 @@ {{ item[textKey] }}
- add + add
From a0b3960ee416ffd641e257b487171cc3a54817ab Mon Sep 17 00:00:00 2001 From: Greg Lorenzen Date: Thu, 31 Oct 2024 16:29:48 +0000 Subject: [PATCH 03/55] Fix enter key and focus for edit modal --- client/components/ui/MultiSelectQueryInput.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/ui/MultiSelectQueryInput.vue b/client/components/ui/MultiSelectQueryInput.vue index d0bdcef2..fe7187ee 100644 --- a/client/components/ui/MultiSelectQueryInput.vue +++ b/client/components/ui/MultiSelectQueryInput.vue @@ -6,7 +6,7 @@
- edit + edit close
{{ item[textKey] }} From 0812e189f74cb0cf176acf7924fb3b91a419a093 Mon Sep 17 00:00:00 2001 From: Greg Lorenzen Date: Thu, 7 Nov 2024 03:38:30 +0000 Subject: [PATCH 04/55] Add keyboard input to MultiSelect component --- client/components/ui/MultiSelect.vue | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client/components/ui/MultiSelect.vue b/client/components/ui/MultiSelect.vue index 37018262..da4bcc13 100644 --- a/client/components/ui/MultiSelect.vue +++ b/client/components/ui/MultiSelect.vue @@ -5,9 +5,9 @@
-
+
edit - close + close
{{ item }}
@@ -66,7 +66,8 @@ export default { typingTimeout: null, isFocused: false, menu: null, - filteredItems: null + filteredItems: null, + inputFocused: false } }, watch: { @@ -129,6 +130,9 @@ export default { }, 100) this.setInputWidth() }, + setInputFocused(focused) { + this.inputFocused = focused + }, setInputWidth() { setTimeout(() => { var value = this.$refs.input.value From 61729881cb0bfca2f7a22da06597713acbc043b2 Mon Sep 17 00:00:00 2001 From: Nicholas Wallace Date: Sat, 7 Dec 2024 16:52:31 -0700 Subject: [PATCH 05/55] Change: no compression when downloading library item as zip file --- server/utils/zipHelpers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/utils/zipHelpers.js b/server/utils/zipHelpers.js index c1617272..44b65296 100644 --- a/server/utils/zipHelpers.js +++ b/server/utils/zipHelpers.js @@ -7,7 +7,7 @@ module.exports.zipDirectoryPipe = (path, filename, res) => { res.attachment(filename) const archive = archiver('zip', { - zlib: { level: 9 } // Sets the compression level. + zlib: { level: 0 } // Sets the compression level. }) // listen for all archive data to be written @@ -49,4 +49,4 @@ module.exports.zipDirectoryPipe = (path, filename, res) => { archive.finalize() }) -} \ No newline at end of file +} From ba55413e63230311ba8c66cae93a6daa91dec1f9 Mon Sep 17 00:00:00 2001 From: mikiher Date: Mon, 16 Dec 2024 19:21:44 +0200 Subject: [PATCH 06/55] LazyBookshelf optimizations --- client/components/app/LazyBookshelf.vue | 141 +++++++++++++----------- 1 file changed, 74 insertions(+), 67 deletions(-) diff --git a/client/components/app/LazyBookshelf.vue b/client/components/app/LazyBookshelf.vue index d27ecdca..6e3083e0 100644 --- a/client/components/app/LazyBookshelf.vue +++ b/client/components/app/LazyBookshelf.vue @@ -65,7 +65,12 @@ export default { tempIsScanning: false, cardWidth: 0, cardHeight: 0, - resizeObserver: null + resizeObserver: null, + lastScrollTop: 0, + lastTimestamp: 0, + postScrollTimeout: null, + currFirstEntityIndex: -1, + currLastEntityIndex: -1 } }, watch: { @@ -354,50 +359,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 +413,6 @@ export default { return } this.destroyEntityComponents() - this.entityIndexesMounted = [] - this.entityComponentRefs = {} this.pagesLoaded = {} this.entities = [] this.totalShelves = 0 @@ -416,40 +422,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 +500,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 +671,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 +693,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 +717,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 +750,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 +813,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 From 91f17efd5f8f2982d5ee5bc30a116ac0d14b4c09 Mon Sep 17 00:00:00 2001 From: Brinly Date: Tue, 17 Dec 2024 12:42:28 +0100 Subject: [PATCH 07/55] feat: Added Australia and New Zealand podcast regions --- client/plugins/i18n.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/plugins/i18n.js b/client/plugins/i18n.js index 12d2b44b..5f6b1508 100644 --- a/client/plugins/i18n.js +++ b/client/plugins/i18n.js @@ -42,6 +42,7 @@ Vue.prototype.$languageCodeOptions = Object.keys(languageCodeMap).map((code) => // iTunes search API uses ISO 3166 country codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 const podcastSearchRegionMap = { + au: { label: 'Australia' }, br: { label: 'Brasil' }, be: { label: 'België / Belgique / Belgien' }, cz: { label: 'Česko' }, @@ -57,6 +58,7 @@ const podcastSearchRegionMap = { hu: { label: 'Magyarország' }, nl: { label: 'Nederland' }, no: { label: 'Norge' }, + nz: { label: 'New Zealand' }, at: { label: 'Österreich' }, pl: { label: 'Polska' }, pt: { label: 'Portugal' }, From 71b943f4341364b010709a45370fed4e0737dc52 Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 18 Dec 2024 17:44:46 -0600 Subject: [PATCH 08/55] Update mobile toolbar nav to show queue for podcast libraries #3719 --- client/components/app/BookShelfToolbar.vue | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue index 6b5a3cd3..74157b18 100644 --- a/client/components/app/BookShelfToolbar.vue +++ b/client/components/app/BookShelfToolbar.vue @@ -42,6 +42,9 @@

{{ $strings.ButtonAdd }}

+ +

{{ $strings.ButtonDownloadQueue }}

+