diff --git a/src/i18n/en_US.json b/src/i18n/en_US.json index a4282abc..154d7120 100644 --- a/src/i18n/en_US.json +++ b/src/i18n/en_US.json @@ -90,6 +90,9 @@ "term.size": "Size", "term.size.normal": "Normal", "term.size.compact": "Compact", + "term.scroll": "Scroll Mode", + "term.scroll.infinite": "Infinite", + "term.scroll.paged": "${songsPerPage} per page", "term.enable": "Enable", "term.disable": "Disable", "term.enabled": "Enabled", diff --git a/src/main/base/store.ts b/src/main/base/store.ts index a014ae45..27140c80 100644 --- a/src/main/base/store.ts +++ b/src/main/base/store.ts @@ -147,6 +147,7 @@ export class Store { }, "libraryPrefs": { "songs": { + "scroll": "infinite", "sort": "name", "sortOrder": "asc", "size": "normal" diff --git a/src/renderer/assets/angles-left.svg b/src/renderer/assets/angles-left.svg new file mode 100644 index 00000000..cc38eb23 --- /dev/null +++ b/src/renderer/assets/angles-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/assets/angles-right.svg b/src/renderer/assets/angles-right.svg new file mode 100644 index 00000000..7bc040ed --- /dev/null +++ b/src/renderer/assets/angles-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/assets/chevron-right.svg b/src/renderer/assets/chevron-right.svg new file mode 100644 index 00000000..538cc611 --- /dev/null +++ b/src/renderer/assets/chevron-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/less/elements.less b/src/renderer/less/elements.less index 9341a783..771bd0e4 100644 --- a/src/renderer/less/elements.less +++ b/src/renderer/less/elements.less @@ -162,6 +162,32 @@ align-self: center; } +.page-btn { + align-self: center; + height: 32px; +} + +.page-btn img { + height: 100%; + align-self: center; +} + +.md-ico-first { + content: url('./assets/angles-left.svg'); +} + +.md-ico-prev { + content: url('./assets/chevron-left.svg'); +} + +.md-ico-next { + content: url('./assets/chevron-right.svg'); +} + +.md-ico-last { + content: url('./assets/angles-right.svg'); +} + .reload-btn { background: rgb(86 86 86 / 52%); border-radius: 100%; diff --git a/src/renderer/style.css b/src/renderer/style.css index cbeee3c8..ea76326e 100644 --- a/src/renderer/style.css +++ b/src/renderer/style.css @@ -10652,6 +10652,26 @@ fieldset:disabled .btn { margin-bottom: -1.5px; align-self: center; } +.page-btn { + align-self: center; + height: 32px; +} +.page-btn img { + height: 100%; + align-self: center; +} +.md-ico-first { + content: url('./assets/angles-left.svg'); +} +.md-ico-prev { + content: url('./assets/chevron-left.svg'); +} +.md-ico-next { + content: url('./assets/chevron-right.svg'); +} +.md-ico-last { + content: url('./assets/angles-right.svg'); +} .reload-btn { background: rgba(86, 86, 86, 0.52); border-radius: 100%; diff --git a/src/renderer/views/components/mediaitem-list-item.ejs b/src/renderer/views/components/mediaitem-list-item.ejs index 56b0c409..8dcd4bc5 100644 --- a/src/renderer/views/components/mediaitem-list-item.ejs +++ b/src/renderer/views/components/mediaitem-list-item.ejs @@ -1,5 +1,5 @@ \ No newline at end of file + diff --git a/src/renderer/views/pages/library-songs.ejs b/src/renderer/views/pages/library-songs.ejs index a96e1597..e4ef860c 100644 --- a/src/renderer/views/pages/library-songs.ejs +++ b/src/renderer/views/pages/library-songs.ejs @@ -1,5 +1,3 @@ - - @@ -75,21 +120,125 @@ template: '#cider-library-songs', data: function () { return { + // currentPage is oneIndexed + currentPage: 1, library: this.$root.library, mediaItemSize: "compact", + pageSize: 250, prefs: this.$root.cfg.libraryPrefs.songs, - app : this.$root + app: this.$root } }, mounted() { + document.querySelector("#app-content") + .addEventListener("scroll", this.handleScroll) this.$root.getLibrarySongsFull() }, - methods: { - sayHello: function () { - alert('Hello world!'); + destroyed() { + document.querySelector("#app-content") + .removeEventListener("scroll", this.handleScroll) + }, + watch: { + 'library.songs.displayListing.length': function () { + if (this.isInfinite) { + // If a search reduces the number of things to show, we want to limit + // the number of songs shown as well. This is to prevent you scrolling + // to load your entire library, searching for one song, and then having + // th re-render the entire library + if (this.currentPage > this.numPages) { + this.currentPage = this.numPages; + } + } }, - play: function () { + 'prefs.scroll': function () { + // When changing modes, set the page to 1. This is primarily to + // prevent going to a high page (e.g., 50) and then switching to infinite + // and showing 12.5k songs + this.currentPage = 1; + } + }, + computed: { + isInfinite: function () { + return this.prefs.scroll === "infinite" + }, + currentSlice: function () { + if (this.isInfinite) { + return this.library.songs.displayListing.slice( + 0, this.currentPage * this.pageSize + ); + } else { + const startingPage = Math.min(this.numPages, this.currentPage); + return this.library.songs.displayListing.slice( + (startingPage - 1) * this.pageSize, + startingPage * this.pageSize + ); + } + }, + numPages: function () { + return Math.ceil(this.library.songs.displayListing.length / this.pageSize) || 1; + }, + pagesToShow: function () { + let start = this.currentPage - 2; + let end = this.currentPage + 2; + + if (start < 1) { + end += (1 - start); + start = 1; + } + + const endDifference = end - this.numPages; + if (endDifference > 0) { + end = this.numPages; + start = Math.max(1, start - endDifference); + } + + const array = []; + for (let idx = start; idx <= end; idx++) { + array.push(idx); + } + return array; + } + }, + methods: { + // Infinite Scrolling + handleScroll: function (event) { + if (this.isInfinite && + this.currentPage < this.numPages && + event.target.scrollTop >= event.target.scrollHeight - event.target.clientHeight) { + this.currentPage += 1; + } + }, + // Pagination + isCurrentPage: function (idx) { + return idx === this.currentPage || + (idx === this.numPages && this.currentPage > this.numPages); + }, + changePage: function (event) { + const value = event.target.valueAsNumber; + + if (!isNaN(value) && value >= 1 && value <= this.numPages) { + this.currentPage = value; + } + }, + goToPage: function (page) { + this.currentPage = page; + }, + goToPrevious: function () { + if (this.currentPage > 1) { + this.currentPage -= 1; + } + }, + goToNext: function () { + if (this.currentPage < this.numPages) { + this.currentPage += 1; + } + }, + goToEnd: function () { + this.currentPage = this.numPages; + }, + // Miscellaneous + play: function () { function shuffleArray(array) { for (var i = array.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); @@ -101,11 +250,11 @@ let query = this.app.library.songs.displayListing.map(item => new MusicKit.MediaItem(item)); if (!app.mk.queue.isEmpty) - app.mk.queue.splice(0, app.mk.queue._itemIDs.length); + app.mk.queue.splice(0, app.mk.queue._itemIDs.length); app.mk.stop().then(() => { - if (app.mk.shuffleMode == 1) { - shuffleArray(query) - } + if (app.mk.shuffleMode == 1) { + shuffleArray(query) + } app.mk.queue.append(query) app.mk.changeToMediaAtIndex(0) });