Files
FrankandClaude Sonnet 5 8b8f67c1d0 Art: drag pieces into a new order
Each gallery tile gets a small drag handle (top-left, ✱). Grab it and
drop it anywhere among the others - the tile moves live as you drag
over neighbours, then the new order is saved automatically on drop.

- Artwork gets a `position` column; the gallery lists by position (was
  upload date). New uploads land after everything else.
- New POST /art/reorder endpoint takes an ordered list of ids (one
  shared CSRF token, "reorder-art") and rewrites positions 1..n.
- Migration backfills position from the existing upload-date order via
  ROW_NUMBER(), then drops the bootstrap default so it matches the
  Doctrine mapping.
- New Stimulus controller (art-reorder): native HTML5 drag/drop, no
  library. dragover live-reorders the DOM by comparing cursor x to the
  hovered tile's midpoint; dragend posts the resulting id order and
  shows a small "Saving order… / Order saved" status pill.

Verified: schema validate clean: a throwaway script exercised
persist+reload of a reordered set against the real DB, and a live
browser check dispatched real DragEvents through the actual Stimulus
controller and confirmed the exact POST payload it sent. phpunit 9/9.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 17:18:02 +02:00

48 lines
2.6 KiB
Twig

{% extends 'base.html.twig' %}
{% block title %}Art · dmtools{% endblock %}
{% block body %}
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h3 mb-0">Art</h1>
<a href="{{ path('app_art_upload') }}" class="btn btn-primary btn-sm">Upload art</a>
</div>
{% if artworks is empty %}
<div class="card shadow-sm">
<div class="card-body text-center text-body-secondary py-5">
Nothing uploaded yet. Use <strong>Upload art</strong> to add maps, handouts or character pieces.
</div>
</div>
{% else %}
<p class="text-body-secondary small mb-2">Drag the <span class="art-drag-handle d-inline-flex">&#10021;</span> handle to reorder.</p>
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4 g-3 art-grid"
data-controller="art-reorder"
data-art-reorder-url-value="{{ path('app_art_reorder') }}"
data-art-reorder-token-value="{{ csrf_token('reorder-art') }}"
data-action="dragover->art-reorder#dragOver drop->art-reorder#drop">
{% for art in artworks %}
<div class="col art-tile" data-art-reorder-target="item" data-art-id="{{ art.id }}">
<div class="card shadow-sm h-100 position-relative">
<button type="button" class="art-drag-handle" draggable="true"
data-action="dragstart->art-reorder#dragStart dragend->art-reorder#dragEnd"
title="Drag to reorder" aria-label="Drag to reorder {{ art.name }}">&#10021;</button>
<a href="{{ path('app_art_show', {id: art.id}) }}" class="text-decoration-none text-body">
<div class="art-thumb">
<img src="{{ path('app_art_file', {id: art.id}) }}" alt="{{ art.name }}" loading="lazy">
</div>
<div class="card-body py-2">
<div class="fw-semibold text-truncate" title="{{ art.name }}">{{ art.name }}</div>
<div class="text-body-secondary small">
{{ art.uploadedAt|date('Y-m-d') }} · {{ (art.size / 1024)|round }} KB
</div>
</div>
</a>
</div>
</div>
{% endfor %}
</div>
<div class="art-reorder-status" data-art-reorder-target="status" aria-live="polite"></div>
{% endif %}
{% endblock %}