diff --git a/BACKLOG.md b/BACKLOG.md index 09d68f7..db139ac 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -23,7 +23,7 @@ bottom as they come up. - [x] Toast queue (stack multiple toasts instead of replacing). ## Features -- [ ] Drag-to-reorder videos within a playlist. +- [x] Drag-to-reorder videos within a playlist. - [ ] "Up next" queue panel showing the autoplay queue. - [ ] Persistent mini now-playing bar when browsing other views. - [ ] Cache size cap in Settings (auto-evict oldest past a limit). diff --git a/frontend/app.js b/frontend/app.js index 7e042c1..e98aa1b 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -51,6 +51,7 @@ let searchResults = []; let queue = []; // list of video objects for autoplay let queueIndex = -1; let current = null; // { meta, qualities, audioUrl, localUrl? } +let dragSource = -1; // index of card being dragged let saveTimer = null; const cachedIds = new Set(); // video ids that exist in the offline cache const downloading = new Set(); // video ids with an in-flight download @@ -889,6 +890,39 @@ function renderCard(v, index, list) { e.stopPropagation(); 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; } @@ -1173,6 +1207,11 @@ function wireUI() { }); $('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(); + }); } // ============================================================================ diff --git a/frontend/styles.css b/frontend/styles.css index 802d60f..06dfac7 100644 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -751,6 +751,10 @@ input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.25); } .card:hover .card-menu { opacity: 1; } .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-badge { position: absolute; left: 5px; bottom: 5px; diff --git a/releases/YT Player_1.0.0_x64-setup.exe b/releases/YT Player_1.0.0_x64-setup.exe index efa4f3d..1fbcc96 100755 Binary files a/releases/YT Player_1.0.0_x64-setup.exe and b/releases/YT Player_1.0.0_x64-setup.exe differ diff --git a/releases/YT Player_1.0.0_x64_en-US.msi b/releases/YT Player_1.0.0_x64_en-US.msi index 78bfa3c..278bccd 100755 Binary files a/releases/YT Player_1.0.0_x64_en-US.msi and b/releases/YT Player_1.0.0_x64_en-US.msi differ diff --git a/releases/ytplayer.exe b/releases/ytplayer.exe index 6ed967c..8faa226 100644 Binary files a/releases/ytplayer.exe and b/releases/ytplayer.exe differ