From 932b1e5b534a09be41075d18847cecf9a7bc5108 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 1 Feb 2026 15:57:18 +0100 Subject: [PATCH] feat: add custom drag-and-drop sorting for playlists Add a new "Custom order" sort option that allows users to manually reorder playlists via drag-and-drop. When this sort mode is active, drag handles appear in front of each playlist item. - Add SortBy.CUSTOM enum value to SortService - Add custom order button to filter-sort-menu - Add isDraggable input and cdkDrag support to PlaylistItemComponent - Enable cdkDropList in RecentPlaylistsComponent when custom sort active - Add translation key for custom order option Co-Authored-By: Claude Opus 4.5 --- .../filter-sort-menu.component.html | 20 +++++++++++++++++++ apps/web/src/assets/i18n/en.json | 3 ++- libs/services/src/lib/sort.service.ts | 15 +++++++++++++- .../playlist-item.component.html | 12 ++++++++++- .../playlist-item.component.scss | 6 ++++++ .../playlist-item/playlist-item.component.ts | 3 +++ .../recent-playlists.component.html | 8 +++++++- .../recent-playlists.component.ts | 18 +++++++++++++++-- 8 files changed, 79 insertions(+), 6 deletions(-) diff --git a/apps/web/src/app/shared/components/filter-sort-menu/filter-sort-menu.component.html b/apps/web/src/app/shared/components/filter-sort-menu/filter-sort-menu.component.html index 0037a9e76..269f0cdb0 100644 --- a/apps/web/src/app/shared/components/filter-sort-menu/filter-sort-menu.component.html +++ b/apps/web/src/app/shared/components/filter-sort-menu/filter-sort-menu.component.html @@ -112,6 +112,26 @@ >check + diff --git a/apps/web/src/assets/i18n/en.json b/apps/web/src/assets/i18n/en.json index 6b7f1ac5d..fb4a0e27b 100644 --- a/apps/web/src/assets/i18n/en.json +++ b/apps/web/src/assets/i18n/en.json @@ -129,7 +129,8 @@ "NAME_ASC": "Name (A-Z)", "NAME_DESC": "Name (Z-A)", "NEWEST": "Date added (Newest first)", - "OLDEST": "Date added (Oldest first)" + "OLDEST": "Date added (Oldest first)", + "CUSTOM_ORDER": "Custom order (Drag & Drop)" }, "PLAYLIST_TYPES": { "M3U": "M3U (local, url, text)", diff --git a/libs/services/src/lib/sort.service.ts b/libs/services/src/lib/sort.service.ts index 8d4919e4e..4f374fef7 100644 --- a/libs/services/src/lib/sort.service.ts +++ b/libs/services/src/lib/sort.service.ts @@ -5,6 +5,7 @@ import { PlaylistMeta } from 'shared-interfaces'; export enum SortBy { DATE_ADDED = 'date', NAME = 'name', + CUSTOM = 'custom', } export enum SortOrder { @@ -53,7 +54,8 @@ export class SortService { // Validate that the saved options match our expected types if ( (parsedOptions.by === SortBy.DATE_ADDED || - parsedOptions.by === SortBy.NAME) && + parsedOptions.by === SortBy.NAME || + parsedOptions.by === SortBy.CUSTOM) && (parsedOptions.order === SortOrder.ASC || parsedOptions.order === SortOrder.DESC) ) { @@ -98,9 +100,20 @@ export class SortService { const dateA = new Date(a.importDate).getTime(); const dateB = new Date(b.importDate).getTime(); comparison = dateA - dateB; + } else if (by === SortBy.CUSTOM) { + // Sort by position field (lower position comes first) + const posA = a.position ?? Number.MAX_SAFE_INTEGER; + const posB = b.position ?? Number.MAX_SAFE_INTEGER; + comparison = posA - posB; + // Custom sort always uses ASC order + return comparison; } return order === SortOrder.ASC ? comparison : -comparison; }); } + + isCustomSort(): boolean { + return this.sortOptions.getValue().by === SortBy.CUSTOM; + } } diff --git a/libs/ui/components/src/lib/recent-playlists/playlist-item/playlist-item.component.html b/libs/ui/components/src/lib/recent-playlists/playlist-item/playlist-item.component.html index 57aa8269f..b9d5e1d92 100644 --- a/libs/ui/components/src/lib/recent-playlists/playlist-item/playlist-item.component.html +++ b/libs/ui/components/src/lib/recent-playlists/playlist-item/playlist-item.component.html @@ -1,4 +1,14 @@ - + + @if (isDraggable()) { + drag_indicator + } @if (item.url) {
(); readonly playlistClicked = output(); diff --git a/libs/ui/components/src/lib/recent-playlists/recent-playlists.component.html b/libs/ui/components/src/lib/recent-playlists/recent-playlists.component.html index 183533ea8..90b045c70 100644 --- a/libs/ui/components/src/lib/recent-playlists/recent-playlists.component.html +++ b/libs/ui/components/src/lib/recent-playlists/recent-playlists.component.html @@ -13,7 +13,12 @@ } } @else { - + @for ( item of data.playlists; track item._id; @@ -22,6 +27,7 @@ + this.currentSortOptions().by === SortBy.CUSTOM; + readonly searchQuery = new BehaviorSubject(''); readonly ghostElements = new Array(10);