mirror of
https://github.com/screentinker/screentinker.git
synced 2026-05-15 07:32:23 -06:00
Playlist: add up/down reorder buttons
Adds accessible up/down arrow buttons alongside the existing drag-to- reorder handle on each playlist item. Touch users (and keyboard users) now have a reliable way to re-order without relying on HTML5 drag-drop, which is effectively unusable on mobile. First/last items have the respective arrow disabled. Uses the same /reorder API the drag handler uses, so behavior stays consistent. flex-wrap on the item container prevents control overflow on narrow screens. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
06d3e93e21
commit
8cd5dd518a
|
|
@ -294,7 +294,7 @@ function renderItems(items) {
|
||||||
}
|
}
|
||||||
|
|
||||||
itemsEl.innerHTML = items.map((item, i) => `
|
itemsEl.innerHTML = items.map((item, i) => `
|
||||||
<div class="playlist-item" data-item-id="${item.id}" draggable="true" style="background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:12px 16px;display:flex;align-items:center;gap:12px;cursor:grab;transition:border-color 0.15s">
|
<div class="playlist-item" data-item-id="${item.id}" data-index="${i}" draggable="true" style="background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:12px 16px;display:flex;align-items:center;gap:12px;cursor:grab;transition:border-color 0.15s;flex-wrap:wrap">
|
||||||
<div style="color:var(--text-muted);font-size:12px;min-width:24px;text-align:center;user-select:none">${i + 1}</div>
|
<div style="color:var(--text-muted);font-size:12px;min-width:24px;text-align:center;user-select:none">${i + 1}</div>
|
||||||
<div style="width:48px;height:36px;border-radius:4px;overflow:hidden;background:var(--bg-input);flex-shrink:0;display:flex;align-items:center;justify-content:center">
|
<div style="width:48px;height:36px;border-radius:4px;overflow:hidden;background:var(--bg-input);flex-shrink:0;display:flex;align-items:center;justify-content:center">
|
||||||
${item.thumbnail_path
|
${item.thumbnail_path
|
||||||
|
|
@ -311,9 +311,17 @@ function renderItems(items) {
|
||||||
<input type="number" class="input item-duration" data-item-id="${item.id}" value="${item.duration_sec}" min="1" style="width:60px;padding:4px 8px;font-size:13px;text-align:center">
|
<input type="number" class="input item-duration" data-item-id="${item.id}" value="${item.duration_sec}" min="1" style="width:60px;padding:4px 8px;font-size:13px;text-align:center">
|
||||||
<span style="font-size:12px;color:var(--text-muted)">sec</span>
|
<span style="font-size:12px;color:var(--text-muted)">sec</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-icon item-remove" data-item-id="${item.id}" title="Remove" style="color:var(--text-muted);background:none;border:none;cursor:pointer;padding:4px;border-radius:4px">
|
<div style="display:flex;align-items:center;gap:4px;flex-shrink:0">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
<button class="btn-icon item-move" data-item-id="${item.id}" data-dir="up" title="Move up" aria-label="Move up" ${i === 0 ? 'disabled' : ''} style="color:var(--text-muted);background:none;border:none;cursor:pointer;padding:4px;border-radius:4px;${i === 0 ? 'opacity:0.3;cursor:not-allowed' : ''}">
|
||||||
</button>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="18 15 12 9 6 15"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="btn-icon item-move" data-item-id="${item.id}" data-dir="down" title="Move down" aria-label="Move down" ${i === items.length - 1 ? 'disabled' : ''} style="color:var(--text-muted);background:none;border:none;cursor:pointer;padding:4px;border-radius:4px;${i === items.length - 1 ? 'opacity:0.3;cursor:not-allowed' : ''}">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="btn-icon item-remove" data-item-id="${item.id}" title="Remove" aria-label="Remove item" style="color:var(--text-muted);background:none;border:none;cursor:pointer;padding:4px;border-radius:4px">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
|
|
@ -348,6 +356,28 @@ function renderItems(items) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Up/down reorder (touch-friendly alternative to drag)
|
||||||
|
itemsEl.querySelectorAll('.item-move').forEach(btn => {
|
||||||
|
btn.addEventListener('click', async (e) => {
|
||||||
|
if (btn.disabled) return;
|
||||||
|
const itemId = parseInt(e.currentTarget.dataset.itemId, 10);
|
||||||
|
const dir = e.currentTarget.dataset.dir;
|
||||||
|
const order = Array.from(itemsEl.querySelectorAll('.playlist-item'))
|
||||||
|
.map(el => parseInt(el.dataset.itemId, 10));
|
||||||
|
const idx = order.indexOf(itemId);
|
||||||
|
const swap = dir === 'up' ? idx - 1 : idx + 1;
|
||||||
|
if (swap < 0 || swap >= order.length) return;
|
||||||
|
[order[idx], order[swap]] = [order[swap], order[idx]];
|
||||||
|
try {
|
||||||
|
const updated = await api.reorderPlaylistItems(currentPlaylistId, order);
|
||||||
|
renderItems(updated);
|
||||||
|
refreshAfterMutation();
|
||||||
|
} catch (err) {
|
||||||
|
showToast(err.message, 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Drag-to-reorder
|
// Drag-to-reorder
|
||||||
setupDragReorder(itemsEl);
|
setupDragReorder(itemsEl);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue