+
⋮⋮
{{ idx + 1 }}
![]()
@@ -69,6 +77,57 @@ import { withBase } from '@/utils/basePath'
// Slide currently hovered, shown enlarged. Null when the cursor is elsewhere.
const previewSlide = ref(null)
+// Drag reordering. dragIndex is the row being carried, dropIndex the row it is
+// currently over. Both null when no drag is in progress.
+const dragIndex = ref(null)
+const dropIndex = ref(null)
+
+function onDragStart(idx, event) {
+ dragIndex.value = idx
+ // The hover preview would otherwise sit over the list for the whole drag.
+ previewSlide.value = null
+ if (event.dataTransfer) {
+ event.dataTransfer.effectAllowed = 'move'
+ // Firefox starts no drag at all unless some data is set.
+ event.dataTransfer.setData('text/plain', String(idx))
+ }
+}
+
+function onDragLeave(idx) {
+ // Only clear when leaving the row that is actually marked, or the row being
+ // entered next would clear its own highlight.
+ if (dropIndex.value === idx) dropIndex.value = null
+}
+
+function onDragEnd() {
+ dragIndex.value = null
+ dropIndex.value = null
+}
+
+async function onDrop(target) {
+ const from = dragIndex.value
+ onDragEnd()
+ if (from === null || from === target) return
+ await reorderTo(from, target)
+}
+
+// Shared by the drag handler and the up/down buttons, so both persist the same
+// way and a failure recovers the same way.
+async function reorderTo(from, to) {
+ const arr = slides.value.slice()
+ const [item] = arr.splice(from, 1)
+ arr.splice(to, 0, item)
+ slides.value = arr
+ try {
+ await slidesApi.reorder(surface.value, arr.map(s => s.filename))
+ } catch (err) {
+ console.error('Reorder failed:', err)
+ // The list on screen no longer matches the server; reload rather than
+ // leave an order that looks saved and is not.
+ await load()
+ }
+}
+
const surfaces = [
{ key: 'lobby', label: 'Lobby Display' },
{ key: 'shopfloor', label: 'Shopfloor Screensaver' }
@@ -120,16 +179,7 @@ async function onUpload(event) {
async function move(idx, delta) {
const target = idx + delta
if (target < 0 || target >= slides.value.length) return
- const arr = slides.value.slice()
- const [item] = arr.splice(idx, 1)
- arr.splice(target, 0, item)
- slides.value = arr
- try {
- await slidesApi.reorder(surface.value, arr.map(s => s.filename))
- } catch (err) {
- console.error('Reorder failed:', err)
- await load()
- }
+ await reorderTo(idx, target)
}
async function deleteSelected() {
@@ -200,6 +250,28 @@ onMounted(load)
font-weight: 700;
color: var(--text-light);
}
+.grip {
+ cursor: grab;
+ color: var(--text-light);
+ letter-spacing: -3px;
+ user-select: none;
+ padding: 0 0.25rem;
+}
+
+.slide-row {
+ transition: background 0.12s, opacity 0.12s;
+}
+
+.slide-row.dragging {
+ opacity: 0.4;
+}
+
+/* A line on the edge being dropped against, rather than moving rows around
+ under the cursor - that reads as the list fighting the drag. */
+.slide-row.drop-target {
+ box-shadow: inset 0 2px 0 0 var(--primary);
+}
+
.thumb {
width: 120px;
height: 68px;