feat(projekt): Anheftungen per Drag & Drop zwischen Unterreitern verschieben
Pin-Zeilen in der Ordneransicht sind draggable, die Reiter sind Ablageziele.
Ein Drop schickt PATCH /api/projects/:id/pins/:pinId {tab_id}; movePin schreibt
project_pins.tab_id um. Verschoben wird nur die Referenz.
- Zielebene hat die Aufnahme schon: Quell-Pin loeschen statt an UNIQUE uniq_pin
scheitern (merged:true)
- Zielreiter serverseitig gegen den Ordner pruefen, sonst 404
- .ptab-n braucht pointer-events:none, sonst flackert die Ablagemarkierung
Verifiziert gegen die laufende App inkl. 404- und Merge-Faellen; Testdaten
zurueckgesetzt. Doku: Kapitel 10 + Changelog.
This commit is contained in:
@@ -3,6 +3,39 @@
|
||||
Chronik der Entwicklungsschritte. **Regel: jede zukünftige Änderung bekommt hier einen Eintrag** (Datum,
|
||||
Was, Warum, betroffene Dateien/Backups).
|
||||
|
||||
## 2026-08-14 — Angeheftete Aufnahmen per Drag & Drop zwischen Unterreitern verschieben
|
||||
|
||||
**Wunsch:** eine im Projekt abgelegte Aufnahme sollte sich innerhalb des Ordners umhängen lassen, z. B. von
|
||||
`DFG/Projektleiter Runden` nach `DFG/INF-Projektboard`. Bisher ging das nur über Lösen + neu Anheften,
|
||||
also zwei Dialoge für eine Bewegung.
|
||||
|
||||
**Umsetzung:** die Pin-Zeilen in der Ordneransicht sind `draggable`, die Reiter der Reiterleiste sind
|
||||
Ablageziele (`bindPinDrag` in `main.js`). Ein Drop schickt `PATCH /api/projects/:id/pins/:pinId {tab_id}`;
|
||||
`db.movePin` schreibt `project_pins.tab_id` um. Verschoben wird ausschließlich die Referenz — an der
|
||||
Aufnahme, ihrer Analyse oder ihren übrigen Anheftungen ändert sich nichts.
|
||||
|
||||
Drei Punkte, die beim Bauen aufgefallen sind:
|
||||
|
||||
- Hängt die Aufnahme auf der Zielebene bereits, würde das `UPDATE` an `UNIQUE uniq_pin` scheitern.
|
||||
`movePin` löscht dann den Quell-Pin (**Zusammenführen statt Fehler**) und meldet `{merged:true}`.
|
||||
- Der Zielreiter wird serverseitig gegen den Ordner geprüft (`project_tabs.project_id`), sonst könnte ein
|
||||
veralteter Browser-Tab einen Pin in ein fremdes Projekt schieben → sonst 404.
|
||||
- `.ptab-n` (Zähler im Reiter) braucht `pointer-events: none`, sonst feuert beim Überfahren des Zählers
|
||||
ein `dragleave` und die Ablagemarkierung flackert.
|
||||
|
||||
**Verifiziert** direkt gegen die laufende App (Ordner „DFG", Pin #12): Verschieben Reiter 2 → 3 → 2 jeweils
|
||||
`{"moved":true}` und `tab_id` in der DB entsprechend; fremder Reiter (999) und fremdes Projekt (2) je
|
||||
`404 {"error":"Anheftung oder Zielreiter nicht gefunden"}`; gleicher Reiter `{"moved":false}` ohne
|
||||
Schreibzugriff; Zusammenführen getestet mit Aufnahme #109 (zwei Pins → einer). Testdaten danach auf den
|
||||
Ausgangsstand zurückgesetzt. `vite build` sauber, `systemctl restart callrec` erst nach Prüfung auf
|
||||
`analysis_status='pending'` (keine laufende Analyse, keine laufende Aufnahme).
|
||||
|
||||
Dateien: `app/db.js` (`movePin`), `app/server.js` (`PATCH …/pins/:pinId`), `app/web/src/main.js`
|
||||
(`bindPinDrag`, `draggable` an der Pin-Zeile, Hinweistext in der Ebenen-Kopfzeile),
|
||||
`app/web/src/style.css` (`.ptab.drop`, `.item.rec.dragging`, `.ptab-n`).
|
||||
Board-Backups: `.bak-movepin` (db.js, server.js, web/src/main.js, web/src/style.css, web/dist).
|
||||
Kapitel 10 (neuer Abschnitt „Verschieben per Drag & Drop" + Sequenzdiagramm + API-Tabelle).
|
||||
|
||||
## 2026-08-10 — Recorder überlebt das Neuladen (GUI holt den Zustand vom Server)
|
||||
|
||||
**Symptom:** während einer laufenden Aufnahme klickte der Nutzer in eine andere Aufnahme; danach stand der
|
||||
|
||||
@@ -96,6 +96,49 @@ Ein Klick auf eine angeheftete Aufnahme springt in den Reiter „Aufnahmen" und
|
||||
Detailbereich (`jumpToRecording`, dieselbe Mechanik wie der Sprung aus einem Suchtreffer, nur ohne
|
||||
Zeitmarke).
|
||||
|
||||
## Verschieben per Drag & Drop (2026-08-14)
|
||||
|
||||
Eine angeheftete Aufnahme lässt sich in der Ordneransicht auf einen **anderen Reiter der Reiterleiste
|
||||
ziehen** und wechselt damit die Ebene — z. B. von „Projektleiter Runden" nach „INF-Projektboard".
|
||||
Verschoben wird nur der Pin (`project_pins.tab_id`); die Aufnahme selbst wird nicht angefasst.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as Nutzer
|
||||
participant G as GUI (main.js)
|
||||
participant S as server.js
|
||||
participant D as MariaDB
|
||||
U->>G: dragstart auf 📌-Zeile
|
||||
Note over G: merkt pin_id,<br/>setzt dataTransfer (Firefox braucht das)
|
||||
U->>G: dragover Zielreiter
|
||||
G-->>U: Reiter gestrichelt markiert (.ptab.drop)<br/>eigener Reiter = kein Ziel
|
||||
U->>G: drop
|
||||
G->>S: PATCH /api/projects/:id/pins/:pinId {tab_id}
|
||||
S->>D: db.movePin()
|
||||
alt Zielreiter gehört nicht zum Ordner / Pin unbekannt
|
||||
D-->>S: null
|
||||
S-->>G: 404
|
||||
else Aufnahme hängt dort schon
|
||||
D->>D: DELETE Quell-Pin (Zusammenführen)
|
||||
S-->>G: {moved:true, merged:true}
|
||||
else Normalfall
|
||||
D->>D: UPDATE project_pins SET tab_id
|
||||
S-->>G: {moved:true}
|
||||
end
|
||||
G->>G: renderProjectDetail() neu zeichnen
|
||||
```
|
||||
|
||||
Drei Details, die nicht offensichtlich sind:
|
||||
|
||||
- **Zusammenführen statt Duplikat:** hängt dieselbe Aufnahme auf der Zielebene bereits, würde das `UPDATE`
|
||||
an `UNIQUE uniq_pin` scheitern. `movePin` löscht in diesem Fall den Quell-Pin, statt einen Fehler zu
|
||||
werfen — im Ergebnis ist die Aufnahme genau einmal auf der Zielebene, was der Nutzer erwartet hat.
|
||||
- **Ziel wird serverseitig geprüft:** `tab_id` muss ein Reiter *desselben* Ordners sein (oder 0 für die
|
||||
Ordner-Ebene), sonst 404. Ein veralteter Reiter im offenen Browser-Tab kann so keinen Pin in ein
|
||||
fremdes Projekt schieben.
|
||||
- **`.ptab-n` (der Zähler im Reiter) bekommt `pointer-events: none`.** Sonst feuert beim Überfahren des
|
||||
Zählers ein `dragleave` am Reiter und die Ablagemarkierung flackert.
|
||||
|
||||
## API
|
||||
|
||||
| Route | Zweck |
|
||||
@@ -109,13 +152,15 @@ Zeitmarke).
|
||||
| `PATCH /api/projects/:id/tabs/:tid` | Unterreiter umbenennen; `:tid = 0` benennt die Ordner-Ebene um (leerer Name = zurück auf „Ordner") |
|
||||
| `DELETE /api/projects/:id/tabs/:tid` | Unterreiter löschen |
|
||||
| `POST /api/projects/:id/pins` | anheften `{recording_id, tab_id}` (`tab_id` fehlt/0 = Ordner-Ebene) |
|
||||
| `PATCH /api/projects/:id/pins/:pinId` | Anheftung auf eine andere Ebene desselben Ordners verschieben `{tab_id}` (Drag & Drop); 404 bei fremdem Reiter/Ordner, `{merged:true}` wenn dort schon vorhanden |
|
||||
| `DELETE /api/projects/:id/pins/:pinId` | Anheftung lösen |
|
||||
| `GET /api/recordings/:id/pins` | wo hängt diese Aufnahme überall |
|
||||
| `GET /api/pins/counts` | `{recording_id: anzahl}` für die ganze Liste |
|
||||
|
||||
## Dateien
|
||||
|
||||
`app/db.js` (Tabellen + CRUD), `app/server.js` (Routen), `app/web/src/main.js`
|
||||
(`viewProjects`/`renderProjectList`/`renderProjectDetail`/`pinPickRecordingDialog`/`pinToProjectDialog`/
|
||||
`openModal`), `app/web/src/style.css` (Ordnergitter, Reiterleiste, Dialog).
|
||||
Board-Backups: `.bak-projects`.
|
||||
`app/db.js` (Tabellen + CRUD, `movePin`), `app/server.js` (Routen), `app/web/src/main.js`
|
||||
(`viewProjects`/`renderProjectList`/`renderProjectDetail`/`bindPinDrag`/`pinPickRecordingDialog`/
|
||||
`pinToProjectDialog`/`openModal`), `app/web/src/style.css` (Ordnergitter, Reiterleiste, `.ptab.drop`,
|
||||
`.item.rec.dragging`, Dialog).
|
||||
Board-Backups: `.bak-projects` (Grundfunktion), `.bak-movepin` (Drag & Drop).
|
||||
|
||||
Reference in New Issue
Block a user