From 5fd0161bc70b7372f46eea3f7a8048f8c43c21ec Mon Sep 17 00:00:00 2001 From: Karim Date: Fri, 3 Jul 2026 21:39:56 +0200 Subject: [PATCH] Kopfzeile der Zeichenflaeche: Dokument-Tabs statt Geschoss-Eigenschaften MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die alte Eigenschaften-Leiste (Geschosshoehe/Schnitthoehe/OKFF) weicht einer Tab-Leiste 'Zeichnungen': ein Tab je Geschoss-Grundriss, Klick wechselt die aktive Zeichnungsebene. Bindet an activeLevelId/setActiveLevelId — dieselbe Store-Quelle wie das Panel 'Zeichnungsebenen', beide bleiben synchron. Geschosshoehe/Schnitthoehe werden in den Geschoss-Einstellungen gepflegt. Der Drawing-Typ (kind: 'plan') und der switch beim Aktivieren lassen spaeter Schnitt-/Layout-Tabs zu, ohne die Leiste umzubauen. --- src/App.tsx | 89 +++++++++++++++++++------------------------- src/DrawingTabs.tsx | 90 +++++++++++++++++++++++++++++++++++++++++++++ src/i18n/de.ts | 3 ++ src/i18n/en.ts | 3 ++ src/styles.css | 84 +++++++++++++++++------------------------- 5 files changed, 167 insertions(+), 102 deletions(-) create mode 100644 src/DrawingTabs.tsx diff --git a/src/App.tsx b/src/App.tsx index a05f5ce..4fa61b6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -156,6 +156,8 @@ import { } from "./state/visibilitySets"; // Seiteneffekt: registriert die zwei dockbaren Kern-Panels in der Registry. import { BUILTIN_PANEL_IDS } from "./panels/builtinPanels"; +import { DrawingTabs, deriveDrawings } from "./DrawingTabs"; +import type { Drawing } from "./DrawingTabs"; /** * Offenes Kontextmenü — genau eine Quelle. `x/y` sind Viewport-Pixel (clientX/Y). @@ -1593,7 +1595,6 @@ export default function App() { // Panels/Menüs/Editoren weiter. const toggleLevelVisible = useStore((s) => s.toggleLevelVisible); const toggleCategoryVisible = useStore((s) => s.toggleCategoryVisible); - const patchActiveFloor = useStore((s) => s.patchActiveFloor); const addFloor = useStore((s) => s.addFloor); const addDrawing = useStore((s) => s.addDrawing); const addDrawingLevel = useStore((s) => s.addDrawingLevel); @@ -2195,6 +2196,14 @@ export default function App() { // referenzstabil → triggert sonst keinen Neuaufbau). const displayKey = `${drawingLevelsMode}|${layersMode}|${activeLevelId}`; + // Dokument-Tabs der Zeichenflächen-Kopfzeile — ein Tab je Geschoss- + // Grundriss (siehe DrawingTabs.tsx). Teilt sich `activeLevelId`/ + // `setActiveLevelId` mit dem Panel „Zeichnungsebenen". + const drawings = useMemo( + () => deriveDrawings(project.drawingLevels), + [project.drawingLevels], + ); + // ── Ableitungen für Oberleiste + Status-Leiste ──────────────────────────── // Eine zoom-/cursor-fähige Plan-Ansicht ist nur im Grundriss eines Geschosses // aktiv. Sonst sind Cursor/Zoom bedeutungslos (PlanView ist nicht montiert → @@ -2794,7 +2803,8 @@ export default function App() { view3d={view3d} fov={fov} mono={planColorMode === "mono"} - onPatchFloor={patchActiveFloor} + drawings={drawings} + onSelectLevel={setActiveLevelId} floorDisplay={floorDisplay} categoryDisplay={categoryDisplay} displayKey={displayKey} @@ -3732,7 +3742,8 @@ function Content({ view3d, fov, mono, - onPatchFloor, + drawings, + onSelectLevel, floorDisplay, categoryDisplay, displayKey, @@ -3790,7 +3801,11 @@ function Content({ fov: number; /** Grundriss in Schwarz-Weiss (mono) statt mit Kategorie-/Element-Farben. */ mono: boolean; - onPatchFloor: (patch: Partial) => void; + /** Dokument-Tabs der Kopfzeile — ein Tab je Geschoss-Grundriss. */ + drawings: Drawing[]; + /** Aktiviert eine Zeichnungsebene (Geschoss) — geteilt mit dem Panel + * „Zeichnungsebenen"; siehe DrawingTabs.tsx. */ + onSelectLevel: (id: string) => void; floorDisplay: DisplayResolver; categoryDisplay: CategoryDisplayResolver & DisplayResolver; displayKey: string; @@ -3853,6 +3868,11 @@ function Content({ if (level.kind === "section" || level.kind === "elevation") { return (
+ + - + {viewType === "perspektive" ? (
@@ -4008,52 +4039,6 @@ function Content({ ); } -/** Kompakte Eigenschaften-Leiste eines Geschosses. */ -function FloorProps({ - level, - onPatch, -}: { - level: DrawingLevel; - onPatch: (patch: Partial) => void; -}) { - return ( -
- - - - {t("props.okff")} - - {t("props.okffValue", { value: (level.baseElevation ?? 0).toFixed(2) })} - - -
- ); -} - /** Abgeleiteter Grundriss eines Geschosses. */ function LevelPlanView({ project, diff --git a/src/DrawingTabs.tsx b/src/DrawingTabs.tsx new file mode 100644 index 0000000..c9208dd --- /dev/null +++ b/src/DrawingTabs.tsx @@ -0,0 +1,90 @@ +// Dokument-Tabs „Zeichnungen/Blätter" — Kopfzeile über der Zeichenfläche. +// +// Ersetzt die frühere Kopfzeile mit Geschosshöhe/Schnitthöhe (diese Werte +// werden jetzt ausschliesslich in den Geschoss-Einstellungen gepflegt, siehe +// den Editor `editor.floorHeight`/`editor.cutHeight` in App.tsx). Ein Tab +// entspricht heute immer dem Grundriss eines Geschosses; ein Klick aktiviert +// dessen Zeichnungsebene über denselben State, den auch das Panel +// „Zeichnungsebenen" (DrawingLevelsPanel) verwendet — beide bleiben so +// zwangsläufig synchron. +// +// Der Typ `Drawing` ist bewusst schlank gehalten, damit künftige Arten +// (Schnitt-Blatt, Layout) sich später einhängen können, ohne dass die +// Tab-Leiste selbst umgebaut werden muss — siehe `activate()` unten. +// +// Bezeichner englisch, UI-Text/Kommentare deutsch (CONVENTIONS.md). + +import type { DrawingLevel } from "./model/types"; +import { t } from "./i18n"; + +/** Art einer Dokument-Zeichnung. Heute nur der Grundriss eines Geschosses; + * Platz für künftige Arten (z. B. "section", "layout") bleibt offen. */ +export type DrawingKind = "plan"; + +/** Ein Tab der Dokument-Leiste — leichtgewichtiger Verweis auf die Quelle + * (aktuell immer eine Zeichnungsebene vom Typ "floor"). */ +export interface Drawing { + id: string; + title: string; + kind: DrawingKind; + /** Geschoss/Zeichnungsebene, deren Grundriss dieser Tab zeigt. */ + levelId: string; +} + +/** Leitet die Dokument-Tabs aus den Geschossen des Projekts ab — ein Tab je + * Geschoss. Schnitte/Ansichten und freie Zeichnungen bleiben vorerst aussen + * vor (weiterhin nur über das Panel „Zeichnungsebenen" erreichbar). */ +export function deriveDrawings(levels: DrawingLevel[]): Drawing[] { + return levels + .filter((level): level is DrawingLevel & { kind: "floor" } => level.kind === "floor") + .map((level) => ({ + id: level.id, + title: level.name, + kind: "plan", + levelId: level.id, + })); +} + +export function DrawingTabs({ + drawings, + activeLevelId, + onSelectLevel, +}: { + drawings: Drawing[]; + /** Aktuell aktive Zeichnungsebene (globaler App-State, siehe App.tsx). */ + activeLevelId: string; + /** Setter derselben Zeichnungsebene — identisch mit dem, den das Panel + * „Zeichnungsebenen" verwendet (kein paralleler State). */ + onSelectLevel: (id: string) => void; +}) { + if (drawings.length === 0) return null; + + // Aktivierung nach Art dispatchen. Heute gibt es nur "plan" (→ Geschoss + // aktivieren); künftige Arten hängen hier ihr eigenes Ziel ein, ohne dass + // sich an der Tab-Leiste selbst etwas ändert. + const activate = (drawing: Drawing) => { + switch (drawing.kind) { + case "plan": + onSelectLevel(drawing.levelId); + break; + } + }; + + return ( +
+ {drawings.map((d) => ( + + ))} +
+ ); +} diff --git a/src/i18n/de.ts b/src/i18n/de.ts index a1cebbc..dc72ac9 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -191,6 +191,9 @@ export const de = { "status.rendererWasm": "Engine", "status.rendererWasmUnavailable": "WebGPU nicht verfügbar", + // ── Dokument-Tabs (Zeichnungen/Blätter) ───────────────────────────────── + "tabs.drawings": "Zeichnungen", + // ── Navigator-Panels ───────────────────────────────────────────────────── "nav.drawingLevels": "Zeichnungsebenen", "nav.layers": "Ebenen", diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 604d84a..c0f2794 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -190,6 +190,9 @@ export const en: Record = { "status.rendererWasm": "Engine", "status.rendererWasmUnavailable": "WebGPU unavailable", + // Document tabs (drawings/sheets). + "tabs.drawings": "Drawings", + // Navigator panels. "nav.drawingLevels": "Drawing levels", "nav.layers": "Layers", diff --git a/src/styles.css b/src/styles.css index 91e90f2..8a35346 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1306,66 +1306,50 @@ body { min-width: 0; } -/* Eigenschaften-Leiste eines Geschosses. */ -.props-strip { +/* Dokument-Tabs „Zeichnungen/Blätter" — Kopfzeile über der Zeichenfläche. + Leiste auf Ebene 1 (--panel), der aktive Tab hebt sich auf Ebene 2 an und + verschmilzt unten mit dem Grund der Zeichenfläche darunter (--bg), wie bei + klassischen Dokument-Tabs. */ +.drawing-tabs { display: flex; - align-items: center; - gap: 18px; - padding: 8px 16px; + align-items: flex-end; + gap: 2px; + padding: 6px 10px 0; border-bottom: 1px solid var(--line); background: var(--panel); + overflow-x: auto; + scrollbar-width: thin; } -.prop { - display: inline-flex; - align-items: center; - gap: 8px; -} - -.prop-label { - font-size: 10px; +.drawing-tab { + border: 1px solid transparent; + border-bottom: none; + background: transparent; + color: var(--muted); + font-family: inherit; + font-size: 12px; font-weight: 600; - letter-spacing: 0.06em; - text-transform: uppercase; - color: var(--muted); + letter-spacing: 0.01em; + padding: 7px 16px; + border-radius: 8px 8px 0 0; + cursor: pointer; + white-space: nowrap; + transition: background 0.14s, color 0.14s, border-color 0.14s; } - -.prop-input { - display: inline-flex; - align-items: center; - gap: 4px; -} - -.prop-input input { - width: 64px; - font-family: var(--font-mono); - font-size: 12px; +.drawing-tab:hover { + background: var(--panel-2); color: var(--ink); - background: var(--input); - border: 1px solid var(--border); - border-radius: 999px; - padding: 4px 10px; - text-align: right; - outline: none; - transition: border-color 0.14s, box-shadow 0.14s; } -.prop-input input:hover { - border-color: var(--accent); +.drawing-tab.active { + background: var(--bg); + border-color: var(--line); + color: var(--ink); + /* Nahtloser Übergang: die Unterkante verschwindet im Zeichenflächen-Grund. */ + position: relative; + top: 1px; } -.prop-input input:focus { - border-color: var(--accent); - box-shadow: 0 0 0 2px var(--accent-dim); -} - -.prop-unit { - font-size: 11px; - color: var(--muted); -} - -.prop-readonly .prop-value { - font-family: var(--font-mono); - font-size: 12px; - color: var(--ink-2); +.drawing-tab-label { + pointer-events: none; } /* Eine Ansicht füllt die Restfläche. */