import { Controller } from '@hotwired/stimulus'; /* * Drag-to-reorder for the art gallery. * *
*
* * ... *
* ... *
*
* * The dragged tile is moved live in the DOM as the pointer crosses other * tiles (dragover, bound on the grid itself via data-action); the new order * is only sent to the server once, on dragend. */ export default class extends Controller { static targets = ['item', 'status']; static values = { url: String, token: String }; dragStart(event) { const tile = event.currentTarget.closest('[data-art-reorder-target="item"]'); if (!tile) return; this.dragEl = tile; this.startOrder = this.currentOrder(); tile.classList.add('is-dragging'); event.dataTransfer.effectAllowed = 'move'; // Firefox requires data to be set for the drag to start at all. event.dataTransfer.setData('text/plain', tile.dataset.artId); } dragOver(event) { if (!this.dragEl) return; event.preventDefault(); const over = event.target.closest('[data-art-reorder-target="item"]'); if (!over || over === this.dragEl) return; const rect = over.getBoundingClientRect(); const before = event.clientX - rect.left < rect.width / 2; over.parentNode.insertBefore(this.dragEl, before ? over : over.nextSibling); } drop(event) { event.preventDefault(); } dragEnd() { if (!this.dragEl) return; this.dragEl.classList.remove('is-dragging'); const order = this.currentOrder(); const changed = order.join(',') !== (this.startOrder || []).join(','); this.dragEl = null; if (changed) this.persist(order); } currentOrder() { return this.itemTargets.map((el) => el.dataset.artId); } async persist(order) { this.showStatus('Saving order…'); try { const res = await fetch(this.urlValue, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ _token: this.tokenValue, order }), }); if (!res.ok) throw new Error(`HTTP ${res.status}`); this.showStatus('Order saved'); } catch (e) { console.error('art reorder save failed', e); this.showStatus('Could not save the new order — reload and try again', true); } } showStatus(text, isError = false) { if (!this.hasStatusTarget) return; clearTimeout(this.statusTimer); this.statusTarget.textContent = text; this.statusTarget.classList.toggle('is-error', isError); this.statusTarget.classList.add('is-visible'); this.statusTimer = setTimeout(() => this.statusTarget.classList.remove('is-visible'), isError ? 4000 : 1800); } }