From c07dfb942451ef7c60e2a340b017896a637f01be Mon Sep 17 00:00:00 2001 From: Jason Henriquez Date: Wed, 10 Apr 2024 09:52:35 -0500 Subject: [PATCH 1/8] Implement playlist sorting --- .../ft-playlist-add-video-prompt.js | 4 +- src/renderer/store/modules/settings.js | 1 + src/renderer/views/Playlist/Playlist.js | 125 +++++++++++++++++- src/renderer/views/Playlist/Playlist.vue | 16 ++- static/locales/en-US.yaml | 15 ++- 5 files changed, 150 insertions(+), 11 deletions(-) diff --git a/src/renderer/components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.js b/src/renderer/components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.js index 4f5a944e4764e..271c661ea1683 100644 --- a/src/renderer/components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.js +++ b/src/renderer/components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.js @@ -107,7 +107,9 @@ export default defineComponent({ newPlaylistDefaultProperties: function () { return this.$store.getters.getNewPlaylistDefaultProperties }, - + locale: function () { + return this.$i18n.locale.replace('_', '-') + }, processedQuery: function() { return this.query.trim().toLowerCase() }, diff --git a/src/renderer/store/modules/settings.js b/src/renderer/store/modules/settings.js index fcd191079f188..a4f596126af47 100644 --- a/src/renderer/store/modules/settings.js +++ b/src/renderer/store/modules/settings.js @@ -280,6 +280,7 @@ const state = { thumbnailPreference: '', blurThumbnails: false, useProxy: false, + userPlaylistSortOrder: 'custom', useRssFeeds: false, useSponsorBlock: false, videoVolumeMouseScroll: false, diff --git a/src/renderer/views/Playlist/Playlist.js b/src/renderer/views/Playlist/Playlist.js index 936f96abebbdb..90ee8c34f2983 100644 --- a/src/renderer/views/Playlist/Playlist.js +++ b/src/renderer/views/Playlist/Playlist.js @@ -7,6 +7,7 @@ import PlaylistInfo from '../../components/playlist-info/playlist-info.vue' import FtListVideoNumbered from '../../components/ft-list-video-numbered/ft-list-video-numbered.vue' import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue' import FtButton from '../../components/ft-button/ft-button.vue' +import FtSelect from '../../components/ft-select/ft-select.vue' import { getLocalPlaylist, getLocalPlaylistContinuation, @@ -15,6 +16,29 @@ import { import { extractNumberFromString, setPublishedTimestampsInvidious, showToast } from '../../helpers/utils' import { invidiousGetPlaylistInfo, youtubeImageUrlToInvidious } from '../../helpers/api/invidious' +const SORT_BY_VALUES = { + Custom: 'custom', + CustomDescending: 'custom_descending', + DateAddedNewest: 'date_added_descending', + DateAddedOldest: 'date_added_ascending', + DatePublishedNewest: 'date_published_newest', + DatePublishedOldest: 'date_published_oldest', + VideoTitleAscending: 'video_title_ascending', + VideoTitleDescending: 'video_title_descending', + AuthorAscending: 'author_ascending', + AuthorDescending: 'author_descending', +} + +const USER_PLAYLIST_ONLY_SORT_BY_VALUES = [ + SORT_BY_VALUES.DateAddedNewest, + SORT_BY_VALUES.DateAddedOldest +] + +const REMOTE_PLAYLIST_ONLY_SORT_BY_VALUES = [ + SORT_BY_VALUES.DatePublishedNewest, + SORT_BY_VALUES.DatePublishedOldest +] + export default defineComponent({ name: 'Playlist', components: { @@ -23,7 +47,8 @@ export default defineComponent({ 'playlist-info': PlaylistInfo, 'ft-list-video-numbered': FtListVideoNumbered, 'ft-flex-box': FtFlexBox, - 'ft-button': FtButton + 'ft-button': FtButton, + 'ft-select': FtSelect }, beforeRouteLeave(to, from, next) { if (!this.isLoading && !this.isUserPlaylistRequested && to.path.startsWith('/watch') && to.query.playlistId === this.playlistId) { @@ -64,6 +89,7 @@ export default defineComponent({ videoSearchQuery: '', promptOpen: false, + localSortOrder: SORT_BY_VALUES.Custom } }, computed: { @@ -76,6 +102,12 @@ export default defineComponent({ currentInvidiousInstance: function () { return this.$store.getters.getCurrentInvidiousInstance }, + userPlaylistSortOrder: function () { + return this.$store.getters.getUserPlaylistSortOrder + }, + sortOrder: function () { + return this.isUserPlaylistRequested ? this.userPlaylistSortOrder : this.localSortOrder + }, currentLocale: function () { return this.$i18n.locale.replace('_', '-') }, @@ -138,17 +170,59 @@ export default defineComponent({ }, sometimesFilteredUserPlaylistItems() { - if (!this.isUserPlaylistRequested) { return this.playlistItems } - if (this.processedVideoSearchQuery === '') { return this.playlistItems } + if (!this.isUserPlaylistRequested) { return this.sortedPlaylistItems } + if (this.processedVideoSearchQuery === '') { return this.sortedPlaylistItems } - return this.playlistItems.filter((v) => { + return this.sortedPlaylistItems.filter((v) => { return v.title.toLowerCase().includes(this.processedVideoSearchQuery) }) }, + sortByValues() { + const sortByValues = Object.values(SORT_BY_VALUES) + if (!this.isUserPlaylistRequested) { + return sortByValues.filter((k) => !USER_PLAYLIST_ONLY_SORT_BY_VALUES.includes(k)) + } else { + return sortByValues.filter((k) => !REMOTE_PLAYLIST_ONLY_SORT_BY_VALUES.includes(k)) + } + }, + isSortOrderCustom() { + return this.sortOrder === SORT_BY_VALUES.Custom || this.sortOrder === SORT_BY_VALUES.CustomDescending + }, + sortedPlaylistItems: function () { + if (this.sortOrder === SORT_BY_VALUES.Custom) { + return this.playlistItems + } else if (this.sortOrder === SORT_BY_VALUES.CustomDescending) { + return this.playlistItems.toReversed() + } + + return this.playlistItems.toSorted((a, b) => { + switch (this.sortOrder) { + case SORT_BY_VALUES.DateAddedNewest: + return b.timeAdded - a.timeAdded + case SORT_BY_VALUES.DateAddedOldest: + return a.timeAdded - b.timeAdded + case SORT_BY_VALUES.DatePublishedNewest: + return b.published - a.published + case SORT_BY_VALUES.DatePublishedOldest: + return a.published - b.published + case SORT_BY_VALUES.VideoTitleAscending: + return a.title.localeCompare(b.title, this.currentLocale) + case SORT_BY_VALUES.VideoTitleDescending: + return b.title.localeCompare(a.title, this.currentLocale) + case SORT_BY_VALUES.AuthorAscending: + return a.author.localeCompare(b.author, this.currentLocale) + case SORT_BY_VALUES.AuthorDescending: + return b.author.localeCompare(a.author, this.currentLocale) + default: + console.error(`Unknown sortOrder: ${this.sortOrder}`) + return 0 + } + }) + }, visiblePlaylistItems: function () { if (!this.isUserPlaylistRequested) { // No filtering for non user playlists yet - return this.playlistItems + return this.sortedPlaylistItems } if (this.userPlaylistVisibleLimit < this.sometimesFilteredUserPlaylistItems.length) { @@ -160,6 +234,38 @@ export default defineComponent({ processedVideoSearchQuery() { return this.videoSearchQuery.trim().toLowerCase() }, + sortBySelectNames() { + return this.sortByValues.map((k) => { + switch (k) { + case SORT_BY_VALUES.Custom: + return this.isUserPlaylistRequested ? this.$t('Playlist.Sort By.Custom') : this.$t('Playlist.Sort By.Default') + case SORT_BY_VALUES.CustomDescending: + return this.isUserPlaylistRequested ? this.$t('Playlist.Sort By.CustomDescending') : this.$t('Playlist.Sort By.DefaultDescending') + case SORT_BY_VALUES.DateAddedNewest: + return this.$t('Playlist.Sort By.DateAddedNewest') + case SORT_BY_VALUES.DateAddedOldest: + return this.$t('Playlist.Sort By.DateAddedOldest') + case SORT_BY_VALUES.DatePublishedNewest: + return this.$t('Playlist.Sort By.DatePublishedNewest') + case SORT_BY_VALUES.DatePublishedOldest: + return this.$t('Playlist.Sort By.DatePublishedOldest') + case SORT_BY_VALUES.VideoTitleAscending: + return this.$t('Playlist.Sort By.VideoTitleAscending') + case SORT_BY_VALUES.VideoTitleDescending: + return this.$t('Playlist.Sort By.VideoTitleDescending') + case SORT_BY_VALUES.AuthorAscending: + return this.$t('Playlist.Sort By.AuthorAscending') + case SORT_BY_VALUES.AuthorDescending: + return this.$t('Playlist.Sort By.AuthorDescending') + default: + console.error(`Unknown sort: ${k}`) + return k + } + }) + }, + sortBySelectValues() { + return this.sortByValues + }, }, watch: { $route () { @@ -477,9 +583,18 @@ export default defineComponent({ } }, + updateSortOrder: function (sortOrder) { + if (this.isUserPlaylistRequested) { + this.updateUserPlaylistSortOrder(sortOrder) + } else { + this.localSortOrder = sortOrder + } + }, + ...mapActions([ 'updateSubscriptionDetails', 'updatePlaylist', + 'updateUserPlaylistSortOrder', 'removeVideo', ]), diff --git a/src/renderer/views/Playlist/Playlist.vue b/src/renderer/views/Playlist/Playlist.vue index f4e3c183ba67c..e36b09d4977f4 100644 --- a/src/renderer/views/Playlist/Playlist.vue +++ b/src/renderer/views/Playlist/Playlist.vue @@ -45,6 +45,14 @@