mirror of https://github.com/FreeTubeApp/FreeTube
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
This commit is contained in:
parent
ea35a13614
commit
9815ed3da2
|
@ -107,7 +107,9 @@ export default defineComponent({
|
||||||
newPlaylistDefaultProperties: function () {
|
newPlaylistDefaultProperties: function () {
|
||||||
return this.$store.getters.getNewPlaylistDefaultProperties
|
return this.$store.getters.getNewPlaylistDefaultProperties
|
||||||
},
|
},
|
||||||
|
locale: function () {
|
||||||
|
return this.$i18n.locale.replace('_', '-')
|
||||||
|
},
|
||||||
processedQuery: function() {
|
processedQuery: function() {
|
||||||
return this.query.trim().toLowerCase()
|
return this.query.trim().toLowerCase()
|
||||||
},
|
},
|
||||||
|
|
|
@ -280,6 +280,7 @@ const state = {
|
||||||
thumbnailPreference: '',
|
thumbnailPreference: '',
|
||||||
blurThumbnails: false,
|
blurThumbnails: false,
|
||||||
useProxy: false,
|
useProxy: false,
|
||||||
|
userPlaylistSortOrder: 'date_added_descending',
|
||||||
useRssFeeds: false,
|
useRssFeeds: false,
|
||||||
useSponsorBlock: false,
|
useSponsorBlock: false,
|
||||||
videoVolumeMouseScroll: false,
|
videoVolumeMouseScroll: false,
|
||||||
|
|
|
@ -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 FtListVideoNumbered from '../../components/ft-list-video-numbered/ft-list-video-numbered.vue'
|
||||||
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
|
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
|
||||||
import FtButton from '../../components/ft-button/ft-button.vue'
|
import FtButton from '../../components/ft-button/ft-button.vue'
|
||||||
|
import FtSelect from '../../components/ft-select/ft-select.vue'
|
||||||
import FtAutoLoadNextPageWrapper from '../../components/ft-auto-load-next-page-wrapper/ft-auto-load-next-page-wrapper.vue'
|
import FtAutoLoadNextPageWrapper from '../../components/ft-auto-load-next-page-wrapper/ft-auto-load-next-page-wrapper.vue'
|
||||||
import {
|
import {
|
||||||
getLocalPlaylist,
|
getLocalPlaylist,
|
||||||
|
@ -16,6 +17,16 @@ import {
|
||||||
import { extractNumberFromString, setPublishedTimestampsInvidious, showToast } from '../../helpers/utils'
|
import { extractNumberFromString, setPublishedTimestampsInvidious, showToast } from '../../helpers/utils'
|
||||||
import { invidiousGetPlaylistInfo, youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
|
import { invidiousGetPlaylistInfo, youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
|
||||||
|
|
||||||
|
const SORT_BY_VALUES = {
|
||||||
|
DateAddedNewest: 'date_added_descending',
|
||||||
|
DateAddedOldest: 'date_added_ascending',
|
||||||
|
AuthorAscending: 'author_ascending',
|
||||||
|
AuthorDescending: 'author_descending',
|
||||||
|
VideoTitleAscending: 'video_title_ascending',
|
||||||
|
VideoTitleDescending: 'video_title_descending',
|
||||||
|
Custom: 'custom',
|
||||||
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Playlist',
|
name: 'Playlist',
|
||||||
components: {
|
components: {
|
||||||
|
@ -25,6 +36,7 @@ export default defineComponent({
|
||||||
'ft-list-video-numbered': FtListVideoNumbered,
|
'ft-list-video-numbered': FtListVideoNumbered,
|
||||||
'ft-flex-box': FtFlexBox,
|
'ft-flex-box': FtFlexBox,
|
||||||
'ft-button': FtButton,
|
'ft-button': FtButton,
|
||||||
|
'ft-select': FtSelect,
|
||||||
'ft-auto-load-next-page-wrapper': FtAutoLoadNextPageWrapper,
|
'ft-auto-load-next-page-wrapper': FtAutoLoadNextPageWrapper,
|
||||||
},
|
},
|
||||||
beforeRouteLeave(to, from, next) {
|
beforeRouteLeave(to, from, next) {
|
||||||
|
@ -77,6 +89,12 @@ export default defineComponent({
|
||||||
currentInvidiousInstance: function () {
|
currentInvidiousInstance: function () {
|
||||||
return this.$store.getters.getCurrentInvidiousInstance
|
return this.$store.getters.getCurrentInvidiousInstance
|
||||||
},
|
},
|
||||||
|
userPlaylistSortOrder: function () {
|
||||||
|
return this.$store.getters.getUserPlaylistSortOrder
|
||||||
|
},
|
||||||
|
sortOrder: function () {
|
||||||
|
return this.isUserPlaylistRequested ? this.userPlaylistSortOrder : SORT_BY_VALUES.Custom
|
||||||
|
},
|
||||||
currentLocale: function () {
|
currentLocale: function () {
|
||||||
return this.$i18n.locale.replace('_', '-')
|
return this.$i18n.locale.replace('_', '-')
|
||||||
},
|
},
|
||||||
|
@ -138,10 +156,10 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
sometimesFilteredUserPlaylistItems() {
|
sometimesFilteredUserPlaylistItems() {
|
||||||
if (!this.isUserPlaylistRequested) { return this.playlistItems }
|
if (!this.isUserPlaylistRequested) { return this.sortedPlaylistItems }
|
||||||
if (this.processedVideoSearchQuery === '') { return this.playlistItems }
|
if (this.processedVideoSearchQuery === '') { return this.sortedPlaylistItems }
|
||||||
|
|
||||||
return this.playlistItems.filter((v) => {
|
return this.sortedPlaylistItems.filter((v) => {
|
||||||
if (typeof (v.title) === 'string' && v.title.toLowerCase().includes(this.processedVideoSearchQuery)) {
|
if (typeof (v.title) === 'string' && v.title.toLowerCase().includes(this.processedVideoSearchQuery)) {
|
||||||
return true
|
return true
|
||||||
} else if (typeof (v.author) === 'string' && v.author.toLowerCase().includes(this.processedVideoSearchQuery)) {
|
} else if (typeof (v.author) === 'string' && v.author.toLowerCase().includes(this.processedVideoSearchQuery)) {
|
||||||
|
@ -151,10 +169,41 @@ export default defineComponent({
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
sortByValues() {
|
||||||
|
return Object.values(SORT_BY_VALUES)
|
||||||
|
},
|
||||||
|
isSortOrderCustom() {
|
||||||
|
return this.sortOrder === SORT_BY_VALUES.Custom
|
||||||
|
},
|
||||||
|
sortedPlaylistItems: function () {
|
||||||
|
if (this.sortOrder === SORT_BY_VALUES.Custom) {
|
||||||
|
return this.playlistItems
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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 () {
|
visiblePlaylistItems: function () {
|
||||||
if (!this.isUserPlaylistRequested) {
|
if (!this.isUserPlaylistRequested) {
|
||||||
// No filtering for non user playlists yet
|
// No filtering for non user playlists yet
|
||||||
return this.playlistItems
|
return this.sortedPlaylistItems
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.userPlaylistVisibleLimit < this.sometimesFilteredUserPlaylistItems.length) {
|
if (this.userPlaylistVisibleLimit < this.sometimesFilteredUserPlaylistItems.length) {
|
||||||
|
@ -166,6 +215,32 @@ export default defineComponent({
|
||||||
processedVideoSearchQuery() {
|
processedVideoSearchQuery() {
|
||||||
return this.videoSearchQuery.trim().toLowerCase()
|
return this.videoSearchQuery.trim().toLowerCase()
|
||||||
},
|
},
|
||||||
|
sortBySelectNames() {
|
||||||
|
return this.sortByValues.map((k) => {
|
||||||
|
switch (k) {
|
||||||
|
case SORT_BY_VALUES.Custom:
|
||||||
|
return this.$t('Playlist.Sort By.Custom')
|
||||||
|
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.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: {
|
watch: {
|
||||||
$route () {
|
$route () {
|
||||||
|
@ -485,6 +560,7 @@ export default defineComponent({
|
||||||
...mapActions([
|
...mapActions([
|
||||||
'updateSubscriptionDetails',
|
'updateSubscriptionDetails',
|
||||||
'updatePlaylist',
|
'updatePlaylist',
|
||||||
|
'updateUserPlaylistSortOrder',
|
||||||
'removeVideo',
|
'removeVideo',
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,13 @@
|
||||||
max-block-size: 7vh;
|
max-block-size: 7vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sortSelect {
|
||||||
|
/* Put it on the right */
|
||||||
|
margin-inline-start: auto;
|
||||||
|
/* Align with 'More Options' dropdown button */
|
||||||
|
margin-inline-end: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.videoThumbnail) {
|
:deep(.videoThumbnail) {
|
||||||
margin-block-start: auto;
|
margin-block-start: auto;
|
||||||
margin-block-end: auto;
|
margin-block-end: auto;
|
||||||
|
|
|
@ -43,6 +43,15 @@
|
||||||
<template
|
<template
|
||||||
v-if="playlistItems.length > 0"
|
v-if="playlistItems.length > 0"
|
||||||
>
|
>
|
||||||
|
<ft-select
|
||||||
|
v-if="isUserPlaylistRequested && playlistItems.length > 1"
|
||||||
|
class="sortSelect"
|
||||||
|
:value="sortOrder"
|
||||||
|
:select-names="sortBySelectNames"
|
||||||
|
:select-values="sortBySelectValues"
|
||||||
|
:placeholder="$t('Playlist.Sort By.Sort By')"
|
||||||
|
@change="updateUserPlaylistSortOrder"
|
||||||
|
/>
|
||||||
<template
|
<template
|
||||||
v-if="visiblePlaylistItems.length > 0"
|
v-if="visiblePlaylistItems.length > 0"
|
||||||
>
|
>
|
||||||
|
@ -62,8 +71,8 @@
|
||||||
appearance="result"
|
appearance="result"
|
||||||
:always-show-add-to-playlist-button="true"
|
:always-show-add-to-playlist-button="true"
|
||||||
:quick-bookmark-button-enabled="quickBookmarkButtonEnabled"
|
:quick-bookmark-button-enabled="quickBookmarkButtonEnabled"
|
||||||
:can-move-video-up="index > 0 && !playlistInVideoSearchMode"
|
:can-move-video-up="index > 0 && !playlistInVideoSearchMode && isSortOrderCustom"
|
||||||
:can-move-video-down="index < playlistItems.length - 1 && !playlistInVideoSearchMode"
|
:can-move-video-down="index < playlistItems.length - 1 && !playlistInVideoSearchMode && isSortOrderCustom"
|
||||||
:can-remove-from-playlist="true"
|
:can-remove-from-playlist="true"
|
||||||
:video-index="playlistInVideoSearchMode ? playlistItems.findIndex(i => i === item) : index"
|
:video-index="playlistInVideoSearchMode ? playlistItems.findIndex(i => i === item) : index"
|
||||||
:initial-visible-state="index < 10"
|
:initial-visible-state="index < 10"
|
||||||
|
|
|
@ -185,7 +185,6 @@ User Playlists:
|
||||||
|
|
||||||
LatestPlayedFirst: 'Recently Played'
|
LatestPlayedFirst: 'Recently Played'
|
||||||
EarliestPlayedFirst: 'Earliest Played'
|
EarliestPlayedFirst: 'Earliest Played'
|
||||||
|
|
||||||
SinglePlaylistView:
|
SinglePlaylistView:
|
||||||
Search for Videos: Search for Videos
|
Search for Videos: Search for Videos
|
||||||
|
|
||||||
|
@ -872,6 +871,15 @@ Playlist:
|
||||||
View: View
|
View: View
|
||||||
Views: Views
|
Views: Views
|
||||||
Last Updated On: Last Updated On
|
Last Updated On: Last Updated On
|
||||||
|
Sort By:
|
||||||
|
Sort By: Sort By
|
||||||
|
DateAddedNewest: Latest added first
|
||||||
|
DateAddedOldest: Earliest added first
|
||||||
|
AuthorAscending: Author (A-Z)
|
||||||
|
AuthorDescending: Author (Z-A)
|
||||||
|
VideoTitleAscending: Title (A-Z)
|
||||||
|
VideoTitleDescending: Title (Z-A)
|
||||||
|
Custom: Custom
|
||||||
|
|
||||||
# On Video Watch Page
|
# On Video Watch Page
|
||||||
#* Published
|
#* Published
|
||||||
|
|
Loading…
Reference in New Issue