// Oberleiste (Top-Bar) — MVP nach docs/design/topbar-footer.md. // // Gruppen (links → rechts): Marke · Ansichts-Umschalter (Grundriss/Perspektive) // · Detailgrad (grob/mittel/fein) · Massstab „1:N" + Zoom (Einpassen/Auswahl/ // 100 %) · Render-Modus · Referenzlinien-Toggle · Layout-Menü · Ressourcen. // // Die Leiste ist rein gesteuert (controlled): alle Zustände kommen als Props // aus App, Aktionen melden sich über Callbacks zurück. Bezeichner englisch, // UI-Text/Kommentare deutsch (CONVENTIONS.md). import type { ReactNode } from "react"; import type { Project } from "../model/types"; import { t } from "../i18n"; import { useEffect, useRef, useState } from "react"; import { Dropdown } from "./Dropdown"; import type { DropdownItem, DropdownOption } from "./Dropdown"; import { WindowControls } from "./WindowControls"; import { applyMark, applyPreset, commonMarkValue, DEFAULT_LINE_HEIGHT, DEFAULT_PRESETS, docLength, isMarkActive, toggleMark, } from "../text/richText"; import type { Align, BoolMark, RichTextDoc, TextRange, } from "../text/richText"; /** Ansichtstyp eines Geschosses (wie in App). */ export type ViewType = "grundriss" | "perspektive"; /** * Detailgrad der Darstellung (≙ DOSSIER Einfach/Standard/Detail). Wirkt im * Grundriss auf Tür-/Fenstersymbole + Schraffuren/Linienfeinheit (siehe * generatePlan.DetailLevel); in der Perspektive noch ohne Wirkung (deferred). */ export type DetailLevel = "grob" | "mittel" | "fein"; /** * Render-/Anzeigemodus der 3D-Ansicht: schattiert, einheitlich weiss (Clay-Look), * texturiert (echte PBR-Materialien je Bauteil), Drahtgitter, Verdeckte Kanten * (Hidden-Line). Wirkt nur in der Perspektive. Der Slot „section" (Schnitt- * schraffur, Welle B) ist bewusst freigelassen. */ export type RenderMode = | "shaded" | "white" | "textured" | "wireframe" | "hidden"; /** * Kanonischer Blickwinkel der 3D-Ansicht (Vectorworks/DOSSIER-Stil). „front", * „top", „side", „iso" springen auf die jeweilige Standardansicht; „perspective" * ist der freie 3/4-Blick. Wirkt nur in der Perspektive. */ export type View3d = "front" | "top" | "side" | "iso" | "perspective"; /** * Farb-Modus des Grundrisses: „color" zeigt Kategorie-/Element-Farben (heutiges * Verhalten), „mono" erzeugt einen reinen Schwarz-Weiss-Plan. Wirkt nur im * Grundriss (orthogonal zum Linien-Modus Display/Print). */ export type PlanColorMode = "color" | "mono"; /** * Massstab-Presets „1:N" (Nenner). `null` steht für „frei" (benutzerdefiniert, * per Eingabe). Quelle für die Anzeige in Top-Bar und Status-Leiste. */ export const SCALE_PRESETS: number[] = [1, 5, 10, 20, 50, 100, 200, 500, 1000]; /** * Gemeinsame Breite (px) von Massstab-Dropdown und Zoom-Segmentpille im * Zoom-Cluster — beide sollen exakt gleich breit sein (Nutzer-Wunsch). */ const ZOOM_CLUSTER_WIDTH = 140; export interface TopBarProps { project: Project; // Ansicht. /** Ob der Ansichts-Umschalter sinnvoll ist (nur für Geschosse). */ viewToggleEnabled: boolean; viewType: ViewType; onViewType: (v: ViewType) => void; // 3D-Ansicht: kanonischer Blickwinkel + Kamera-Sichtwinkel (FOV). Wirkt nur in // der Perspektive; ein Preset-Klick wechselt nötigenfalls auch dorthin. view3d: View3d; onView3d: (v: View3d) => void; /** Sichtwinkel der Perspektivkamera in Grad (Kamera-Einstellungen). */ fov: number; onFov: (n: number) => void; // Zeichenwerkzeuge (Auswahl/Wand/Linie …), Wandtyp-Wahl und Fang-Menü leben // jetzt in der Werkzeug-Palette (ToolsPanel), nicht mehr in der Oberleiste. // Detailgrad. detail: DetailLevel; onDetail: (d: DetailLevel) => void; /** Detailgrad wirkt nur, wenn ein Plan gezeichnet wird (sonst deaktiviert). */ detailEnabled: boolean; // Massstab + Zoom. /** * Gewählter (angewandter) Papier-Massstab-Nenner (1:N) — steuert das Dropdown * und „100 %". Setzt beim Wählen den Plan-Zoom auf exakt 1:N. */ scaleDenominator: number; /** Einen Papier-Massstab anwenden (Preset/frei): setzt N + Plan-Zoom. */ onApplyScale: (n: number) => void; /** * LIVE Papier-Massstab-Nenner aus der aktuellen View-Transform (ändert sich * beim Wheel-Zoom) oder `null`, wenn keine Plan-Ansicht aktiv ist. */ liveScale: number | null; /** Live-Zoom in Prozent (aus der Plan-View-Transform). */ zoomPercent: number; /** Zoom-Aktionen — nur wirksam, wenn eine Plan-Ansicht aktiv ist. */ zoomEnabled: boolean; onFit: () => void; onFitSelection: () => void; onZoom100: () => void; /** Öffnet den PDF-Export-Dialog (nur im Grundriss sinnvoll). */ onExportPdf: () => void; /** Öffnet den DXF-Export-Dialog (nur im Grundriss sinnvoll). */ onExportDxf: () => void; // Darstellungsart — kontextabhängig: Perspektive nutzt `renderMode`, // Grundriss nutzt `planColorMode`. Genau einer der beiden ist je View aktiv. renderMode: RenderMode; onRenderMode: (m: RenderMode) => void; /** Render-Modus nur in der Perspektive sinnvoll (sonst deaktiviert). */ renderModeEnabled: boolean; // Farb-Modus des Grundrisses (Farbig ↔ Schwarz-Weiss). Nur im Grundriss. planColorMode: PlanColorMode; onPlanColorMode: (m: PlanColorMode) => void; // Sichtbarkeits-Kombinationen (Ebenen-/Zeichnungskombinationen). /** Handler-Satz für die beiden Kombinations-Dropdowns. */ combos: ComboMenuHandlers; // Ressourcen-Fenster. resourcesOpen: boolean; onToggleResources: () => void; /** Öffnet das Einstellungs-Fenster (Darstellungsfarben, Projekt-Felder). */ onOpenSettings: () => void; /** Layout-Menü (von App geliefert, damit dessen Logik dort bleibt). */ layoutMenu: ReactNode; // ── Datei-Menü (Burger, ganz rechts) ─────────────────────────────────────── /** Öffnet den DXF/DWG-Import-Datei-Dialog. */ onImport: () => void; /** Projekt als Datei speichern (JSON-Export des gesamten Projekts). */ onSaveProject: () => void; /** Projekt aus einer Datei laden. */ onOpenProject: () => void; // ── Text-Gruppe (Stempel-/Text-Formatierung) ─────────────────────────────── /** * Ziel der Text-Formatierung: `null` = nichts Text-artiges selektiert (die * Controls setzen nur Defaults für neuen Text). Sonst das aktuell selektierte * Rich-Text-Dokument (z. B. Raumstempel) samt optionalem Bereich und einem * Setter. Ist `range` `null`, wirken Aktionen auf das GANZE Doc. */ textTarget: TextTarget | null; } /** * Beschreibt das aktuell formatierbare Rich-Text-Ziel der Oberleiste. `apply` * schreibt das neue Dokument zurück in den App-State (z. B. via setRoomStampDoc). */ export interface TextTarget { doc: RichTextDoc; range: TextRange | null; apply: (doc: RichTextDoc) => void; } /** * Verdrahtung der beiden Kombinations-Dropdowns (Vectorworks-Stil). App liefert * die Speicher-/Lade-/Lösch-Logik; die Komponente bleibt rein gesteuert. */ export interface ComboMenuHandlers { // Ebenen-Kombinationen. listLayerCombos: () => string[]; saveLayerCombo: (name: string) => void; loadLayerCombo: (name: string) => void; deleteLayerCombo: (name: string) => void; // Zeichnungs-Kombinationen. listDrawingCombos: () => string[]; saveDrawingCombo: (name: string) => void; loadDrawingCombo: (name: string) => void; deleteDrawingCombo: (name: string) => void; } /** Kleiner vertikaler Trenner zwischen Gruppen. */ function Sep() { return