8fd8987b70
Neuer GPU-Renderer fuer den Grundriss (src/plan/glPlan/): Earcut-Tessellierung (konkav-faehig), gehrte Linienzuege (Miter), echte Papier-mm-Strichbreiten im Massstab (repliziert den SVG-printStrokeVb-Pfad), Hybrid mit scharfem SVG-Text- Overlay. GPU ist der Standardpfad; der SVG-Renderer bleibt automatischer Fallback, falls WebGL2/Shader nicht verfuegbar sind. Imperativer Pan (rAF + CSS-transform) fuer fluessige Interaktion ohne React-Re-Render je Frame. Enthaelt zudem den bisher nicht committeten Arbeitsstand des Browser-BIM (Oeffnungen, Treppen, Raeume, Decken, DXF-Export, Materialbibliothek, Kontext- Import, Tauri-Compute-Boundary-PoC).
152 lines
7.9 KiB
Markdown
152 lines
7.9 KiB
Markdown
# Oberleiste – Angleichung an DOSSIER (Umsetzungs-Spezifikation)
|
||
|
||
Verbindliche Vorlage für den Umbau der Top-Bar (`src/ui/TopBar.tsx`,
|
||
`src/styles.css`, Verdrahtung in `src/App.tsx`). Quelle: das DOSSIER-Rhino-
|
||
Plugin (`ToolbarApp.jsx`, `components/BarControls.jsx`, `TextEditorApp.jsx`).
|
||
Bezeichner englisch, UI-Text/Kommentare deutsch (CONVENTIONS.md).
|
||
|
||
## 0. Grundprimitive (neu, DOSSIER-konform)
|
||
|
||
Alle Leisten-Controls teilen dieselbe Höhe und Pillenform.
|
||
|
||
- `BAR_H = 22px` Basis-Höhe. Segmentierte Pillen `BAR_H + 2 = 24px`
|
||
(`box-sizing:border-box`, 1px Rand inbegriffen).
|
||
- Pille: `border:1px solid var(--border)`, `border-radius:999px`,
|
||
`background:var(--input)`. Hover (interaktiv): `border-color:var(--accent-border)`,
|
||
`background:var(--accent-dim)`.
|
||
- Aktiver Zustand (Toggle AN / aktive Segmentzelle): `background:var(--accent)`,
|
||
`color:#fff`.
|
||
|
||
### BarCombo (Pillen-Dropdown)
|
||
Wir haben bereits `src/ui/Dropdown.tsx`. Der Dropdown-Trigger MUSS optisch der
|
||
Pille entsprechen (Höhe 24, radius 999, obige Farben). Ein optionales Icon sitzt
|
||
LINKS **ausserhalb** der Pille (18px breit, `var(--muted)`), ein optionaler
|
||
Zahnrad-Knopf („settings") sitzt rechts **innerhalb** der Pille. Prüfen, ob
|
||
`Dropdown` bereits so aussieht; falls nicht → Trigger-CSS angleichen (Klasse
|
||
`tb-dd-trigger`). KEINE zweite Dropdown-Implementierung bauen.
|
||
|
||
### Segmentpille (3er/4er)
|
||
Aussencontainer `display:inline-flex; height:24px; border:1px solid var(--border);
|
||
border-radius:999px; overflow:hidden`. Zellen ohne eigenen Radius; interne Trenner
|
||
über `border-left:1px solid var(--border)` (erste Zelle ohne). Aktive Zelle
|
||
`var(--accent)`/#fff, inaktiv `var(--input)`/`var(--ink)`, Hover
|
||
`var(--accent-dim)`/`var(--accent)`. Genutzt für: Ansichts-Icons, Zoom (%/fit/center),
|
||
**B/I/U**, **L/C/R**.
|
||
|
||
### BarButton (quadratischer Icon-Knopf)
|
||
22×22, `border-radius:999px`, sonst wie Pille. Aktiv = Akzentfüllung, Icon #fff.
|
||
|
||
## 1. Reihenfolge der Gruppen (links → rechts)
|
||
|
||
1. Marke (bestehend, unverändert).
|
||
2. Ansichts-Gruppe (bestehend `view-grid`; Zellen auf Segmentpillen-Look bringen).
|
||
3. Sichtbarkeits-Kombinationen (bestehend, `BarCombo`-Look).
|
||
4. Detailgrad + Massstab (gestapelt, `BarCombo`-Look).
|
||
5. **Massstab/Zoom-Cluster NEU** (siehe §2) — ersetzt die heutige Gruppe mit der
|
||
DOPPELTEN Zoom-Anzeige.
|
||
6. Darstellungsart (bestehend, `BarCombo`).
|
||
7. **Text-Gruppe NEU** (siehe §3) — die zentrale neue Leiste.
|
||
8. Referenzlinien / Linien-Modus (bestehend).
|
||
9. Rechts: Layout · Ressourcen · Projektname.
|
||
|
||
## 2. Massstab/Zoom-Cluster (ersetzt Doppel-Zoom-Bug)
|
||
|
||
HEUTE FALSCH: In `TopBar.tsx` wird `tb-zoom` (Zoom %) ZWEIMAL gerendert
|
||
(einmal im `tb-zoomstack`, einmal darunter als eigener `<span>`). Die zweite,
|
||
lose `<span className="tb-zoom">…%</span>` ersatzlos ENTFERNEN.
|
||
|
||
NEUES Layout — 2×2-Raster (`display:grid; grid-template-columns:auto auto;
|
||
gap:4px 6px; align-items:center`):
|
||
|
||
- **Spalte 1, beide Zeilen** (`grid-row:1 / span 2`): EINE kombinierte Stat-Pille,
|
||
`width:70px`, Höhe `BAR_H*2+6 = 50px`, `border-radius:14px` (NICHT 999),
|
||
`border:1px solid var(--border)`, `background:var(--input)`, Innen zwei Zeilen
|
||
mittig, getrennt durch 1px-Linie (`var(--border)`):
|
||
- oben: Live-Massstab `1:N` (Akzentfarbe, `var(--font-mono)`, 11px, 700)
|
||
- unten: Zoom `NN%` (`var(--ink-2)`, mono, 11px)
|
||
- „am Massstab" (Zoom==gewählter Massstab): Pille `background:var(--accent-dim)`,
|
||
`border-color:var(--accent)`, Text `var(--accent)`.
|
||
- Nicht-Plan-Ansicht: beide Werte „—".
|
||
- **Spalte 2, Zeile 1**: Massstab-Dropdown (`BarCombo`, ~140px, mono) + Print/PDF
|
||
bleibt separat. (Massstab-Dropdown ist der bestehende `scaleOptions`-Dropdown.)
|
||
- **Spalte 2, Zeile 2**: Zoom-Segmentpille mit 3 Zellen — `%` (=`onZoom100`,
|
||
Label „1:1"/100 %), `fit_screen` (=`onFit`), `center_focus_strong`
|
||
(=`onFitSelection`). Material-Icons. Daneben ggf. Referenzlinien-BarButton.
|
||
|
||
Export-Knöpfe (PDF/DXF) wandern in eine eigene kleine BarButton-Reihe rechts vom
|
||
Cluster (Icons `picture_as_pdf` / `download`) ODER bleiben Pillen — Hauptsache
|
||
NICHT mehr Teil des Zoom-Blocks, damit der Cluster ruhig bleibt.
|
||
|
||
## 3. Text-Gruppe in der Oberleiste (NEU – Kern dieser Aufgabe)
|
||
|
||
Immer sichtbar. 3×2-Raster (`grid-template-columns:110px 130px 80px; gap:4px 6px`).
|
||
Setzt Defaults für neuen Text UND formatiert die aktuelle Auswahl live.
|
||
|
||
Zeile 1:
|
||
- **Stil-Preset** `BarCombo` (110px): Optionen aus `DEFAULT_PRESETS`
|
||
(Titel/Untertitel/Label/Notiz) + „— Stil —".
|
||
- **Font** `BarCombo` (130px): Systemfont-Liste (mind. Helvetica, Arial, Inter,
|
||
Times New Roman, Georgia, Courier New). `applyMark(doc,range,'font',v)`.
|
||
- **Grösse** `BarCombo` (80px): Presets in pt `[8,9,10,11,12,14,18,24,36,48]`
|
||
+ „Eigene…" → Zahl-Input-Pille. `applyMark(...,'sizePt',n)`.
|
||
|
||
Zeile 2:
|
||
- **B/I/U** Segmentpille (110px, Icons `format_bold`/`format_italic`/
|
||
`format_underlined`): `toggleMark(doc,range,'bold'|'italic'|'underline')`.
|
||
Aktiv-Zustand aus `isMarkActive(doc,range,mark)`.
|
||
- **L/C/R** Segmentpille (130px, Icons `format_align_left`/`_center`/`_right`):
|
||
setzt `paragraph.align` im Bereich.
|
||
- **„+"-Text-Button** (80px, BarButton/Pille, Icon `add`, Label „Text"): startet
|
||
das Text-Werkzeug (neues Textobjekt platzieren). Falls das Text-Annotation-
|
||
Werkzeug noch nicht existiert, Button vorerst `disabled` mit Tooltip
|
||
(kein stiller No-Op) — aber Verdrahtung vorbereiten.
|
||
|
||
**Auswahl-Bewusstsein (WICHTIG):** Prop `textTarget` (oder aus App-State): entweder
|
||
`null` (nichts Text-artiges selektiert → Controls setzen nur Defaults, Ränder
|
||
normal) ODER `{ doc: RichTextDoc, range: TextRange|null, apply: (doc)=>void }`
|
||
für den aktuell selektierten Raumstempel/Text. Ist `textTarget != null`, tragen
|
||
die Zeile-2-Pillen `border-color:var(--accent)` (Akzent-Glow), und alle Aktionen
|
||
wirken auf `textTarget.doc` via `textTarget.apply(newDoc)`. Ohne aktive Range
|
||
(nur Objekt selektiert, kein Editor offen) wirkt Formatierung auf das GANZE Doc.
|
||
|
||
Quelle der `textTarget`-Daten: der Raum-Agent exponiert Stempel-Doc + Setter
|
||
(`setRoomStampDoc(roomId, doc)`) im App-State (siehe Peer-Absprache). App leitet
|
||
für den selektierten Raum `{doc: room.stampDoc, range: activeStampRange,
|
||
apply: d => setRoomStampDoc(room.id, d)}` an die Text-Gruppe.
|
||
|
||
## 4. Text-Inhalt bearbeiten: kleines Fenster (kein Footer)
|
||
|
||
Doppelklick auf einen Raumstempel/ein Textobjekt öffnet ein **schwebendes
|
||
Dialog-Fenster** (nicht den Footer, kein Panel-Aufklappen). Umsetzung: neue
|
||
Komponente `src/ui/TextEditorDialog.tsx` — ein zentriertes/абgesetztes Fenster
|
||
(~560×420, `--shadow-3`, `border-radius:8px`, Titel „Text bearbeiten",
|
||
Kopf mit Schliessen-✕), Body = der bestehende `src/text/RichTextEditor.tsx`
|
||
(er bringt seine eigene Mini-Toolbar mit — das ist hier ok, weil es ein eigenes
|
||
Fenster ist), Fuss = „Abbrechen" / „Übernehmen". „Übernehmen" ruft
|
||
`setRoomStampDoc(roomId, editedDoc)`.
|
||
|
||
Der Doppelklick-Handler lebt in App (Plan-View/Viewport → onDoubleClick auf
|
||
Stempel-Hit → `openTextEditor(roomId)`), NICHT im Footer. Falls der Raum-Agent
|
||
den Footer benutzt hat: diesen Pfad entfernen und durch den Dialog ersetzen.
|
||
|
||
## 5. Farb-/Stil-Tokens
|
||
|
||
Bestehende CSS-Variablen weiterverwenden (`--panel`,`--input`,`--border`,
|
||
`--accent`,`--accent-dim`,`--accent-border`,`--ink`,`--ink-2`,`--muted`,
|
||
`--font-mono`,`--shadow-1..3`). KEINE neuen Farbwerte hart kodieren. Falls ein
|
||
Token fehlt (z. B. `--accent-border`), prüfen und ggf. aus bestehenden ableiten.
|
||
|
||
## 6. i18n
|
||
|
||
Neue Keys in de.ts UND en.ts: `text.style`, `text.font`, `text.size`,
|
||
`text.size.custom`, `text.bold/italic/underline`, `text.align.left/center/right`,
|
||
`text.add`, `text.add.hint`, `text.editTitle`, `text.apply`, `text.cancel`,
|
||
`text.selectedHint`. Presets-Namen über bestehende `rt.*`/Preset-Keys, sofern da.
|
||
|
||
## 7. Gate (Pflicht)
|
||
|
||
`rm -f tsconfig.tsbuildinfo && npx tsc -b` grün, `npm run build` grün, keine
|
||
AI-Spuren (grep auf Claude/Anthropic/AI/Generated/Co-Authored), Boot-Probe
|
||
(`node scripts/probe.mjs`) ohne Konsolenfehler, Screenshot der Oberleiste.
|
||
KEIN Commit.
|