feat(gui): Projektansicht mit Ordnern, Unterreitern und angehefteten Aufnahmen

Neuer Reiter neben "Aufnahmen": Projektordner bündeln Aufnahmen zweistufig
(Ordner + Unterreiter), auf beiden Ebenen anheftbar. Anheften geht aus dem
Projekt heraus (Dropdown mit Filter) oder über den 📌-Knopf in der
Aufnahmenliste, der Ordner und Ebene abfragt, neue Ordner anlegt und
bestehende Anheftungen zum Lösen zeigt. Klick auf eine angeheftete Aufnahme
springt in die Aufnahmen-Ansicht.

Ein Pin ist eine Referenz, kein Besitz: die Aufnahme bleibt unverändert in
der Liste und darf in mehreren Ordnern hängen.

project_pins.tab_id ist INT NOT NULL DEFAULT 0 (0 = Ordner-Ebene) statt NULL,
weil MariaDB NULL von UNIQUE-Indizes ausschließt — sonst ließe sich dieselbe
Aufnahme mehrfach auf dieselbe Ebene anheften. So macht INSERT IGNORE das
Anheften idempotent.

purgeRecording räumt project_pins mit ab; getProject blendet Aufnahmen im
Papierkorb aus, ohne deren Pin zu löschen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
schakal00783
2026-08-02 23:05:07 +02:00
co-authored by Claude Opus 5
parent 963ce427da
commit 772385713e
7 changed files with 688 additions and 9 deletions
+50 -1
View File
@@ -500,4 +500,53 @@ body.resizing { cursor: col-resize; user-select: none; }
font-weight: 700;
color: var(--fg);
line-height: 2.2;
}
}
/* ---- Projektansicht ---------------------------------------------------------
Ordnergitter (Ebene 1) und darin eine Reiterleiste für die Unterreiter
(Ebene 2). Die Reiter sehen bewusst anders aus als die Kopfzeilen-Tabs, damit
klar bleibt: das ist eine Ebene INNERHALB eines Ordners. */
.proj-head { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
.proj-head h3 { margin: 0; flex: 1; }
.proj-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 10px; margin-top: 10px; }
.proj-card {
display: flex; align-items: center; gap: 10px; cursor: pointer;
background: var(--surface-2); border: 1px solid var(--line);
border-radius: 10px; padding: 12px 14px; transition: border-color .1s, background .1s;
}
.proj-card:hover { border-color: var(--accent); }
.proj-card .grow { flex: 1; min-width: 0; }
.proj-card b { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.proj-ico { font-size: 22px; flex: none; }
.proj-tabs { display: flex; gap: 6px; flex-wrap: wrap; margin: 14px 0 0; border-bottom: 1px solid var(--line); padding-bottom: 8px; }
.ptab {
background: transparent; border: 1px solid transparent; color: var(--muted);
border-radius: 8px 8px 0 0; padding: 6px 12px; cursor: pointer; font: inherit; font-size: .9rem;
}
.ptab:hover { color: var(--fg); background: var(--surface-2); }
.ptab.active { color: var(--accent); background: var(--accent-soft); border-color: var(--accent); font-weight: 600; }
.ptab.add { color: var(--muted); border-style: dashed; border-color: var(--line); }
.ptab.add:hover { color: var(--accent); border-color: var(--accent); }
.ptab-n { font-size: 11px; opacity: .7; margin-left: 4px; }
.proj-level { margin-top: 14px; }
.proj-level-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
.proj-level-head .grow { flex: 1; }
.proj-level .item.rec { cursor: pointer; }
/* 📌 in der Aufnahmenliste: blass, solange die Aufnahme nirgends hängt. */
.pinbtn { opacity: .45; position: relative; }
.item.rec:hover .pinbtn { opacity: 1; }
.pinbtn.pinned { opacity: 1; border-color: var(--accent); color: var(--accent); }
.pin-n { font-size: 10px; font-weight: 700; margin-left: 2px; vertical-align: super; }
/* ---- Modal ---- */
.modal-back {
position: fixed; inset: 0; background: rgba(0, 0, 0, .5);
display: flex; align-items: center; justify-content: center; z-index: 50; padding: 20px;
}
.modal { width: min(520px, 100%); max-height: 85vh; overflow: auto; margin: 0; }
.modal h3 { margin-top: 0; }
.modal-foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
.modal select[size] { height: auto; padding: 4px; }
.pin-current .item { padding: 6px 10px; }