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);