diff --git a/server/app/templates/index.html b/server/app/templates/index.html index e31d6af..ef6a415 100644 --- a/server/app/templates/index.html +++ b/server/app/templates/index.html @@ -27,7 +27,14 @@ .thumb { width: 160px; max-width: 100%; border-radius: 4px; display: block; } .photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 10px; margin-top: 8px; } - .photo-card { position: relative; cursor: grab; border-radius: 6px; overflow: hidden; aspect-ratio: 1; background: #f3f4f6; border: 1px solid #e5e7eb; } + .photo-card { + position: relative; cursor: grab; border-radius: 6px; overflow: hidden; aspect-ratio: 1; + background: #f3f4f6; border: 1px solid #e5e7eb; + /* Touching a card is what starts a drag -- without this the browser + treats that touch as the start of a page scroll instead, and + pointermove events for the drag never arrive on mobile. */ + touch-action: none; + } .photo-card:active { cursor: grabbing; } .photo-card.dragging { opacity: 0.35; } .photo-card.drag-over { outline: 3px solid #2563eb; outline-offset: -3px; } @@ -158,7 +165,25 @@ }); let upcomingItems = []; - let dragFromIndex = null; + + // Pointer Events (not the native HTML5 Drag-and-Drop API) so the same + // code drives mouse, touch, and pen -- native drag-and-drop is + // mouse-only by spec and never fires at all on phones/tablets. + let dragState = null; // { pointerId, fromIndex, toIndex, moved } + const DRAG_START_THRESHOLD_PX = 6; // ignore tiny jitter from an imprecise tap + + function clearDragOverStyling() { + document.querySelectorAll('.photo-card.drag-over').forEach((el) => el.classList.remove('drag-over')); + } + + function endDrag(card) { + card.classList.remove('dragging'); + clearDragOverStyling(); + if (dragState && dragState.moved && dragState.toIndex !== undefined && dragState.toIndex !== dragState.fromIndex) { + moveItem(dragState.fromIndex, dragState.toIndex); + } + dragState = null; + } function renderUpcoming(items) { upcomingItems = items; @@ -168,7 +193,7 @@ items.forEach((item, i) => { const card = document.createElement('div'); card.className = 'photo-card'; - card.draggable = true; + card.dataset.index = String(i); const img = document.createElement('img'); img.src = item.thumbnail_url; @@ -190,25 +215,48 @@ }); card.appendChild(nextBtn); - card.addEventListener('dragstart', (e) => { - dragFromIndex = i; - card.classList.add('dragging'); - e.dataTransfer.effectAllowed = 'move'; + card.addEventListener('pointerdown', (e) => { + if (e.target.closest('.show-next')) { + return; // let the button's own click handler run, don't start a drag + } + if (e.pointerType === 'mouse' && e.button !== 0) { + return; // left button only + } + dragState = { pointerId: e.pointerId, fromIndex: i, toIndex: undefined, moved: false, startX: e.clientX, startY: e.clientY }; + card.setPointerCapture(e.pointerId); }); - card.addEventListener('dragend', () => { - card.classList.remove('dragging'); - dragFromIndex = null; + + card.addEventListener('pointermove', (e) => { + if (!dragState || dragState.pointerId !== e.pointerId) { + return; + } + if (!dragState.moved) { + const dx = e.clientX - dragState.startX; + const dy = e.clientY - dragState.startY; + if (Math.hypot(dx, dy) < DRAG_START_THRESHOLD_PX) { + return; + } + dragState.moved = true; + card.classList.add('dragging'); + } + const overCard = document.elementFromPoint(e.clientX, e.clientY)?.closest('.photo-card'); + clearDragOverStyling(); + if (overCard && overCard !== card) { + overCard.classList.add('drag-over'); + dragState.toIndex = Number(overCard.dataset.index); + } else { + dragState.toIndex = undefined; + } }); - card.addEventListener('dragover', (e) => { - e.preventDefault(); - card.classList.add('drag-over'); + + card.addEventListener('pointerup', (e) => { + if (dragState && dragState.pointerId === e.pointerId) { + endDrag(card); + } }); - card.addEventListener('dragleave', () => card.classList.remove('drag-over')); - card.addEventListener('drop', (e) => { - e.preventDefault(); - card.classList.remove('drag-over'); - if (dragFromIndex !== null && dragFromIndex !== i) { - moveItem(dragFromIndex, i); + card.addEventListener('pointercancel', (e) => { + if (dragState && dragState.pointerId === e.pointerId) { + endDrag(card); } });