Add drag-to-reorder within playlists
Cards in playlist view are draggable. Visual feedback: opacity on source card, accent border on drop target. Reorder persists to store. Files: - frontend/app.js: dragSource state, drag/drop handlers on cards, container-level dragover guard in wireUI - frontend/styles.css: .card.dragging, .card.drag-over styles
This commit is contained in:
@@ -23,7 +23,7 @@ bottom as they come up.
|
|||||||
- [x] Toast queue (stack multiple toasts instead of replacing).
|
- [x] Toast queue (stack multiple toasts instead of replacing).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- [ ] Drag-to-reorder videos within a playlist.
|
- [x] Drag-to-reorder videos within a playlist.
|
||||||
- [ ] "Up next" queue panel showing the autoplay queue.
|
- [ ] "Up next" queue panel showing the autoplay queue.
|
||||||
- [ ] Persistent mini now-playing bar when browsing other views.
|
- [ ] Persistent mini now-playing bar when browsing other views.
|
||||||
- [ ] Cache size cap in Settings (auto-evict oldest past a limit).
|
- [ ] Cache size cap in Settings (auto-evict oldest past a limit).
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ let searchResults = [];
|
|||||||
let queue = []; // list of video objects for autoplay
|
let queue = []; // list of video objects for autoplay
|
||||||
let queueIndex = -1;
|
let queueIndex = -1;
|
||||||
let current = null; // { meta, qualities, audioUrl, localUrl? }
|
let current = null; // { meta, qualities, audioUrl, localUrl? }
|
||||||
|
let dragSource = -1; // index of card being dragged
|
||||||
let saveTimer = null;
|
let saveTimer = null;
|
||||||
const cachedIds = new Set(); // video ids that exist in the offline cache
|
const cachedIds = new Set(); // video ids that exist in the offline cache
|
||||||
const downloading = new Set(); // video ids with an in-flight download
|
const downloading = new Set(); // video ids with an in-flight download
|
||||||
@@ -889,6 +890,39 @@ function renderCard(v, index, list) {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
openCardMenu(v);
|
openCardMenu(v);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Drag-to-reorder in playlist view
|
||||||
|
if (view.type === 'playlist') {
|
||||||
|
card.draggable = true;
|
||||||
|
card.addEventListener('dragstart', () => {
|
||||||
|
card.classList.add('dragging');
|
||||||
|
dragSource = index;
|
||||||
|
});
|
||||||
|
card.addEventListener('dragend', () => {
|
||||||
|
card.classList.remove('dragging');
|
||||||
|
});
|
||||||
|
card.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
card.classList.add('drag-over');
|
||||||
|
});
|
||||||
|
card.addEventListener('dragleave', () => {
|
||||||
|
card.classList.remove('drag-over');
|
||||||
|
});
|
||||||
|
card.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
card.classList.remove('drag-over');
|
||||||
|
const from = dragSource;
|
||||||
|
const to = index;
|
||||||
|
if (from === to || from < 0) return;
|
||||||
|
const pl = data.playlists.find((p) => p.id === view.id);
|
||||||
|
if (!pl) return;
|
||||||
|
const [moved] = pl.videos.splice(from, 1);
|
||||||
|
pl.videos.splice(to, 0, moved);
|
||||||
|
persist();
|
||||||
|
renderList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1173,6 +1207,11 @@ function wireUI() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('modal').addEventListener('click', (e) => { if (e.target.id === 'modal') closeModal(); });
|
$('modal').addEventListener('click', (e) => { if (e.target.id === 'modal') closeModal(); });
|
||||||
|
|
||||||
|
// Drag-to-reorder: prevent default on cards container
|
||||||
|
els.cards.addEventListener('dragover', (e) => {
|
||||||
|
if (view.type === 'playlist') e.preventDefault();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -751,6 +751,10 @@ input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.25); }
|
|||||||
.card:hover .card-menu { opacity: 1; }
|
.card:hover .card-menu { opacity: 1; }
|
||||||
.card-menu:hover { color: #fff; border-color: var(--accent); background: var(--accent); }
|
.card-menu:hover { color: #fff; border-color: var(--accent); background: var(--accent); }
|
||||||
|
|
||||||
|
/* Drag-to-reorder */
|
||||||
|
.card.dragging { opacity: 0.4; }
|
||||||
|
.card.drag-over { border-color: var(--accent) !important; box-shadow: 0 0 0 2px var(--accent-glow); }
|
||||||
|
|
||||||
/* Saved-offline badge on cards */
|
/* Saved-offline badge on cards */
|
||||||
.saved-badge {
|
.saved-badge {
|
||||||
position: absolute; left: 5px; bottom: 5px;
|
position: absolute; left: 5px; bottom: 5px;
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user