Skip to content

Commit 9815ed3

Browse files
authored
Sort videos within playlist (#4921)
* Implement playlist sorting * Hide sort menu for remote playlists * Remove 'Custom (descending)' sort order I don't see the need for this particular sort order. * Adjust sort order & align dropdown with 'More Options' button * Make 'Latest added first' default option instead of custom * Remove unlikely-to-be-implemented 'Date published' sorting options Context from absidue: 'I don't think we should even attempt to support it, due to all of the situations where it wouldn't be possible.' * Update to use sortOrder as main variable throughout * Hide sort menu for playlists of length <2
1 parent ea35a13 commit 9815ed3

File tree

6 files changed

+111
-8
lines changed

6 files changed

+111
-8
lines changed

src/renderer/components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ export default defineComponent({
107107
newPlaylistDefaultProperties: function () {
108108
return this.$store.getters.getNewPlaylistDefaultProperties
109109
},
110-
110+
locale: function () {
111+
return this.$i18n.locale.replace('_', '-')
112+
},
111113
processedQuery: function() {
112114
return this.query.trim().toLowerCase()
113115
},

src/renderer/store/modules/settings.js

+1
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ const state = {
280280
thumbnailPreference: '',
281281
blurThumbnails: false,
282282
useProxy: false,
283+
userPlaylistSortOrder: 'date_added_descending',
283284
useRssFeeds: false,
284285
useSponsorBlock: false,
285286
videoVolumeMouseScroll: false,

src/renderer/views/Playlist/Playlist.js

+80-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import PlaylistInfo from '../../components/playlist-info/playlist-info.vue'
77
import FtListVideoNumbered from '../../components/ft-list-video-numbered/ft-list-video-numbered.vue'
88
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
99
import FtButton from '../../components/ft-button/ft-button.vue'
10+
import FtSelect from '../../components/ft-select/ft-select.vue'
1011
import FtAutoLoadNextPageWrapper from '../../components/ft-auto-load-next-page-wrapper/ft-auto-load-next-page-wrapper.vue'
1112
import {
1213
getLocalPlaylist,
@@ -16,6 +17,16 @@ import {
1617
import { extractNumberFromString, setPublishedTimestampsInvidious, showToast } from '../../helpers/utils'
1718
import { invidiousGetPlaylistInfo, youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
1819

20+
const SORT_BY_VALUES = {
21+
DateAddedNewest: 'date_added_descending',
22+
DateAddedOldest: 'date_added_ascending',
23+
AuthorAscending: 'author_ascending',
24+
AuthorDescending: 'author_descending',
25+
VideoTitleAscending: 'video_title_ascending',
26+
VideoTitleDescending: 'video_title_descending',
27+
Custom: 'custom',
28+
}
29+
1930
export default defineComponent({
2031
name: 'Playlist',
2132
components: {
@@ -25,6 +36,7 @@ export default defineComponent({
2536
'ft-list-video-numbered': FtListVideoNumbered,
2637
'ft-flex-box': FtFlexBox,
2738
'ft-button': FtButton,
39+
'ft-select': FtSelect,
2840
'ft-auto-load-next-page-wrapper': FtAutoLoadNextPageWrapper,
2941
},
3042
beforeRouteLeave(to, from, next) {
@@ -77,6 +89,12 @@ export default defineComponent({
7789
currentInvidiousInstance: function () {
7890
return this.$store.getters.getCurrentInvidiousInstance
7991
},
92+
userPlaylistSortOrder: function () {
93+
return this.$store.getters.getUserPlaylistSortOrder
94+
},
95+
sortOrder: function () {
96+
return this.isUserPlaylistRequested ? this.userPlaylistSortOrder : SORT_BY_VALUES.Custom
97+
},
8098
currentLocale: function () {
8199
return this.$i18n.locale.replace('_', '-')
82100
},
@@ -138,10 +156,10 @@ export default defineComponent({
138156
},
139157

140158
sometimesFilteredUserPlaylistItems() {
141-
if (!this.isUserPlaylistRequested) { return this.playlistItems }
142-
if (this.processedVideoSearchQuery === '') { return this.playlistItems }
159+
if (!this.isUserPlaylistRequested) { return this.sortedPlaylistItems }
160+
if (this.processedVideoSearchQuery === '') { return this.sortedPlaylistItems }
143161

144-
return this.playlistItems.filter((v) => {
162+
return this.sortedPlaylistItems.filter((v) => {
145163
if (typeof (v.title) === 'string' && v.title.toLowerCase().includes(this.processedVideoSearchQuery)) {
146164
return true
147165
} else if (typeof (v.author) === 'string' && v.author.toLowerCase().includes(this.processedVideoSearchQuery)) {
@@ -151,10 +169,41 @@ export default defineComponent({
151169
return false
152170
})
153171
},
172+
sortByValues() {
173+
return Object.values(SORT_BY_VALUES)
174+
},
175+
isSortOrderCustom() {
176+
return this.sortOrder === SORT_BY_VALUES.Custom
177+
},
178+
sortedPlaylistItems: function () {
179+
if (this.sortOrder === SORT_BY_VALUES.Custom) {
180+
return this.playlistItems
181+
}
182+
183+
return this.playlistItems.toSorted((a, b) => {
184+
switch (this.sortOrder) {
185+
case SORT_BY_VALUES.DateAddedNewest:
186+
return b.timeAdded - a.timeAdded
187+
case SORT_BY_VALUES.DateAddedOldest:
188+
return a.timeAdded - b.timeAdded
189+
case SORT_BY_VALUES.VideoTitleAscending:
190+
return a.title.localeCompare(b.title, this.currentLocale)
191+
case SORT_BY_VALUES.VideoTitleDescending:
192+
return b.title.localeCompare(a.title, this.currentLocale)
193+
case SORT_BY_VALUES.AuthorAscending:
194+
return a.author.localeCompare(b.author, this.currentLocale)
195+
case SORT_BY_VALUES.AuthorDescending:
196+
return b.author.localeCompare(a.author, this.currentLocale)
197+
default:
198+
console.error(`Unknown sortOrder: ${this.sortOrder}`)
199+
return 0
200+
}
201+
})
202+
},
154203
visiblePlaylistItems: function () {
155204
if (!this.isUserPlaylistRequested) {
156205
// No filtering for non user playlists yet
157-
return this.playlistItems
206+
return this.sortedPlaylistItems
158207
}
159208

160209
if (this.userPlaylistVisibleLimit < this.sometimesFilteredUserPlaylistItems.length) {
@@ -166,6 +215,32 @@ export default defineComponent({
166215
processedVideoSearchQuery() {
167216
return this.videoSearchQuery.trim().toLowerCase()
168217
},
218+
sortBySelectNames() {
219+
return this.sortByValues.map((k) => {
220+
switch (k) {
221+
case SORT_BY_VALUES.Custom:
222+
return this.$t('Playlist.Sort By.Custom')
223+
case SORT_BY_VALUES.DateAddedNewest:
224+
return this.$t('Playlist.Sort By.DateAddedNewest')
225+
case SORT_BY_VALUES.DateAddedOldest:
226+
return this.$t('Playlist.Sort By.DateAddedOldest')
227+
case SORT_BY_VALUES.VideoTitleAscending:
228+
return this.$t('Playlist.Sort By.VideoTitleAscending')
229+
case SORT_BY_VALUES.VideoTitleDescending:
230+
return this.$t('Playlist.Sort By.VideoTitleDescending')
231+
case SORT_BY_VALUES.AuthorAscending:
232+
return this.$t('Playlist.Sort By.AuthorAscending')
233+
case SORT_BY_VALUES.AuthorDescending:
234+
return this.$t('Playlist.Sort By.AuthorDescending')
235+
default:
236+
console.error(`Unknown sort: ${k}`)
237+
return k
238+
}
239+
})
240+
},
241+
sortBySelectValues() {
242+
return this.sortByValues
243+
},
169244
},
170245
watch: {
171246
$route () {
@@ -485,6 +560,7 @@ export default defineComponent({
485560
...mapActions([
486561
'updateSubscriptionDetails',
487562
'updatePlaylist',
563+
'updateUserPlaylistSortOrder',
488564
'removeVideo',
489565
]),
490566

src/renderer/views/Playlist/Playlist.scss

+7
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@
6666
max-block-size: 7vh;
6767
}
6868

69+
.sortSelect {
70+
/* Put it on the right */
71+
margin-inline-start: auto;
72+
/* Align with 'More Options' dropdown button */
73+
margin-inline-end: 20px;
74+
}
75+
6976
:deep(.videoThumbnail) {
7077
margin-block-start: auto;
7178
margin-block-end: auto;

src/renderer/views/Playlist/Playlist.vue

+11-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@
4343
<template
4444
v-if="playlistItems.length > 0"
4545
>
46+
<ft-select
47+
v-if="isUserPlaylistRequested && playlistItems.length > 1"
48+
class="sortSelect"
49+
:value="sortOrder"
50+
:select-names="sortBySelectNames"
51+
:select-values="sortBySelectValues"
52+
:placeholder="$t('Playlist.Sort By.Sort By')"
53+
@change="updateUserPlaylistSortOrder"
54+
/>
4655
<template
4756
v-if="visiblePlaylistItems.length > 0"
4857
>
@@ -62,8 +71,8 @@
6271
appearance="result"
6372
:always-show-add-to-playlist-button="true"
6473
:quick-bookmark-button-enabled="quickBookmarkButtonEnabled"
65-
:can-move-video-up="index > 0 && !playlistInVideoSearchMode"
66-
:can-move-video-down="index < playlistItems.length - 1 && !playlistInVideoSearchMode"
74+
:can-move-video-up="index > 0 && !playlistInVideoSearchMode && isSortOrderCustom"
75+
:can-move-video-down="index < playlistItems.length - 1 && !playlistInVideoSearchMode && isSortOrderCustom"
6776
:can-remove-from-playlist="true"
6877
:video-index="playlistInVideoSearchMode ? playlistItems.findIndex(i => i === item) : index"
6978
:initial-visible-state="index < 10"

static/locales/en-US.yaml

+9-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,6 @@ User Playlists:
185185

186186
LatestPlayedFirst: 'Recently Played'
187187
EarliestPlayedFirst: 'Earliest Played'
188-
189188
SinglePlaylistView:
190189
Search for Videos: Search for Videos
191190

@@ -872,6 +871,15 @@ Playlist:
872871
View: View
873872
Views: Views
874873
Last Updated On: Last Updated On
874+
Sort By:
875+
Sort By: Sort By
876+
DateAddedNewest: Latest added first
877+
DateAddedOldest: Earliest added first
878+
AuthorAscending: Author (A-Z)
879+
AuthorDescending: Author (Z-A)
880+
VideoTitleAscending: Title (A-Z)
881+
VideoTitleDescending: Title (Z-A)
882+
Custom: Custom
875883

876884
# On Video Watch Page
877885
#* Published

0 commit comments

Comments
 (0)