Files
dmtools/migrations/Version20260915151210.php
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

45 lines
1.4 KiB
PHP

<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260915151210 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add artwork.position for the drag-to-reorder gallery, backfilled by upload date';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE artwork ADD position INT NOT NULL DEFAULT 0');
// Backfill existing rows so the gallery keeps its current (upload-date) order
// until someone drags a piece; new uploads get their position in PHP.
$this->addSql(<<<'SQL'
UPDATE artwork a
JOIN (
SELECT id, ROW_NUMBER() OVER (ORDER BY uploaded_at ASC, id ASC) AS rn
FROM artwork
) ranked ON ranked.id = a.id
SET a.position = ranked.rn
SQL);
// Drop the bootstrap default now that every row has a real position;
// the mapping has no default, so schema:validate expects none either.
$this->addSql('ALTER TABLE artwork CHANGE position position INT NOT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE artwork DROP position');
}
}