Ribbon: TopBar-Ansichtssteuerung in Ansichten-Tab gemergt; TopBar auf schmale globale Leiste reduziert
This commit is contained in:
+34
-26
@@ -69,7 +69,7 @@ import type { Pick3dHit } from "./viewport/Wasm3DViewport";
|
|||||||
import { ResourceManager } from "./ui/ResourceManager";
|
import { ResourceManager } from "./ui/ResourceManager";
|
||||||
import type { ResourceManagerHandlers } from "./ui/ResourceManager";
|
import type { ResourceManagerHandlers } from "./ui/ResourceManager";
|
||||||
import { HatchSwatch } from "./ui/hatchPreview";
|
import { HatchSwatch } from "./ui/hatchPreview";
|
||||||
import { TopBar } from "./ui/TopBar";
|
import { TopBar, ViewRibbonTab } from "./ui/TopBar";
|
||||||
import { RibbonBar } from "./ui/ribbon/RibbonBar";
|
import { RibbonBar } from "./ui/ribbon/RibbonBar";
|
||||||
import { Dropdown } from "./ui/Dropdown";
|
import { Dropdown } from "./ui/Dropdown";
|
||||||
import type { DropdownItem } from "./ui/Dropdown";
|
import type { DropdownItem } from "./ui/Dropdown";
|
||||||
@@ -3191,8 +3191,41 @@ export default function App() {
|
|||||||
e.target.value = "";
|
e.target.value = "";
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{/* Schmale globale Leiste: Marke/Ressourcen · Text-Formatierung ·
|
||||||
|
Datei/Export/Einstellungen. Ansichts-/Zoom-/Darstellungs-Steuerung ist
|
||||||
|
in den „Ansichten"-Ribbon-Tab gewandert (ViewRibbonTab, unten). */}
|
||||||
<TopBar
|
<TopBar
|
||||||
project={project}
|
project={project}
|
||||||
|
zoomEnabled={planActive}
|
||||||
|
onExportPdf={() => setExportOpen(true)}
|
||||||
|
onExportDxf={() => setExportDxfOpen(true)}
|
||||||
|
onExportSchedule={onExportSchedule}
|
||||||
|
resourcesOpen={resourcesOpen}
|
||||||
|
onToggleResources={() => setResourcesOpen((v) => !v)}
|
||||||
|
onOpenSettings={() => setSettingsOpen(true)}
|
||||||
|
onImport={() => dxfInputRef.current?.click()}
|
||||||
|
onSaveProject={onSaveProject}
|
||||||
|
onOpenProject={onOpenProject}
|
||||||
|
layoutMenu={
|
||||||
|
<LayoutMenu
|
||||||
|
layout={layout}
|
||||||
|
onApply={(l) => setLayout(reconcileLayout(l))}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
textTarget={textTarget}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Ribbon-Oberleiste (2D · 3D · BIM · Ansichten). Der „Ansichten"-Tab trägt
|
||||||
|
die aus der TopBar gemergte Ansichts-/Zoom-/Darstellungs-Steuerung
|
||||||
|
(ViewRibbonTab); die Werkzeug-Sidebar bleibt vorerst erhalten. */}
|
||||||
|
<RibbonBar
|
||||||
|
activeTool={activeTool}
|
||||||
|
activeCommand={engineView.commandName}
|
||||||
|
toolsEnabled={activeLevel.kind === "floor"}
|
||||||
|
onSelectTool={onSelectTool}
|
||||||
|
onRunCommand={onRunCommand}
|
||||||
|
viewsContent={
|
||||||
|
<ViewRibbonTab
|
||||||
viewToggleEnabled={activeLevel.kind === "floor"}
|
viewToggleEnabled={activeLevel.kind === "floor"}
|
||||||
viewType={viewType}
|
viewType={viewType}
|
||||||
onViewType={setViewType}
|
onViewType={setViewType}
|
||||||
@@ -3211,9 +3244,6 @@ export default function App() {
|
|||||||
onFit={fitPlan}
|
onFit={fitPlan}
|
||||||
onFitSelection={fitSelection}
|
onFitSelection={fitSelection}
|
||||||
onZoom100={zoomToApplied}
|
onZoom100={zoomToApplied}
|
||||||
onExportPdf={() => setExportOpen(true)}
|
|
||||||
onExportDxf={() => setExportDxfOpen(true)}
|
|
||||||
onExportSchedule={onExportSchedule}
|
|
||||||
renderMode={renderMode}
|
renderMode={renderMode}
|
||||||
onRenderMode={setRenderMode}
|
onRenderMode={setRenderMode}
|
||||||
renderModeEnabled={viewType === "perspektive"}
|
renderModeEnabled={viewType === "perspektive"}
|
||||||
@@ -3240,30 +3270,8 @@ export default function App() {
|
|||||||
},
|
},
|
||||||
deleteDrawingCombo,
|
deleteDrawingCombo,
|
||||||
}}
|
}}
|
||||||
resourcesOpen={resourcesOpen}
|
|
||||||
onToggleResources={() => setResourcesOpen((v) => !v)}
|
|
||||||
onOpenSettings={() => setSettingsOpen(true)}
|
|
||||||
onImport={() => dxfInputRef.current?.click()}
|
|
||||||
onSaveProject={onSaveProject}
|
|
||||||
onOpenProject={onOpenProject}
|
|
||||||
layoutMenu={
|
|
||||||
<LayoutMenu
|
|
||||||
layout={layout}
|
|
||||||
onApply={(l) => setLayout(reconcileLayout(l))}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
textTarget={textTarget}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Ribbon-Oberleiste (2D · 3D · BIM · Ansichten) — additiv unter der TopBar.
|
|
||||||
Teilt sich denselben Aktivierungs-Pfad wie die Werkzeug-Sidebar; die
|
|
||||||
Sidebar bleibt vorerst erhalten (Plan-Doc Phase 3 entfernt sie). */}
|
|
||||||
<RibbonBar
|
|
||||||
activeTool={activeTool}
|
|
||||||
activeCommand={engineView.commandName}
|
|
||||||
toolsEnabled={activeLevel.kind === "floor"}
|
|
||||||
onSelectTool={onSelectTool}
|
|
||||||
onRunCommand={onRunCommand}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="body">
|
<div className="body">
|
||||||
|
|||||||
@@ -5472,3 +5472,12 @@ body {
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
|
/* „Ansichten"-Tab: die aus der TopBar gemergten Cluster (View-Grid, Kombis,
|
||||||
|
Detail, Zoom, Darstellung) in EINER Zeile — nutzt die bestehenden `.tb-*`-
|
||||||
|
Stile unverändert weiter. */
|
||||||
|
.ribbon-views {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|||||||
+120
-166
@@ -84,49 +84,12 @@ const ZOOM_CLUSTER_WIDTH = 140;
|
|||||||
export interface TopBarProps {
|
export interface TopBarProps {
|
||||||
project: Project;
|
project: Project;
|
||||||
|
|
||||||
// Ansicht.
|
// Ansichts-/Zoom-/Darstellungs-Steuerung ist in den „Ansichten"-Ribbon-Tab
|
||||||
/** Ob der Ansichts-Umschalter sinnvoll ist (nur für Geschosse). */
|
// gewandert (siehe ViewRibbonTab). Die TopBar ist nur noch die schmale globale
|
||||||
viewToggleEnabled: boolean;
|
// Leiste: Marke/Ressourcen · Text-Formatierung · Datei/Export/Einstellungen.
|
||||||
viewType: ViewType;
|
|
||||||
onViewType: (v: ViewType) => void;
|
|
||||||
|
|
||||||
// 3D-Ansicht: kanonischer Blickwinkel + Kamera-Sichtwinkel (FOV). Wirkt nur in
|
/** Ob Zoom/Plan aktiv ist — steuert nur noch, ob Export im Datei-Menü geht. */
|
||||||
// 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;
|
zoomEnabled: boolean;
|
||||||
onFit: () => void;
|
|
||||||
onFitSelection: () => void;
|
|
||||||
onZoom100: () => void;
|
|
||||||
/** Öffnet den PDF-Export-Dialog (nur im Grundriss sinnvoll). */
|
/** Öffnet den PDF-Export-Dialog (nur im Grundriss sinnvoll). */
|
||||||
onExportPdf: () => void;
|
onExportPdf: () => void;
|
||||||
/** Öffnet den DXF-Export-Dialog (nur im Grundriss sinnvoll). */
|
/** Öffnet den DXF-Export-Dialog (nur im Grundriss sinnvoll). */
|
||||||
@@ -134,21 +97,6 @@ export interface TopBarProps {
|
|||||||
/** Lädt die Bauteilliste des Projekts als CSV herunter. */
|
/** Lädt die Bauteilliste des Projekts als CSV herunter. */
|
||||||
onExportSchedule: () => void;
|
onExportSchedule: () => 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.
|
// Ressourcen-Fenster.
|
||||||
resourcesOpen: boolean;
|
resourcesOpen: boolean;
|
||||||
onToggleResources: () => void;
|
onToggleResources: () => void;
|
||||||
@@ -958,8 +906,45 @@ function AppMenu({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TopBar({
|
/**
|
||||||
project,
|
* Props des „Ansichten"-Tabs — die Ansichts-/Zoom-/Darstellungs-Steuerung, die
|
||||||
|
* früher in der Mitte der TopBar sass. Eine Teilmenge von {@link TopBarProps}.
|
||||||
|
*/
|
||||||
|
export interface ViewRibbonTabProps {
|
||||||
|
viewToggleEnabled: boolean;
|
||||||
|
viewType: ViewType;
|
||||||
|
onViewType: (v: ViewType) => void;
|
||||||
|
view3d: View3d;
|
||||||
|
onView3d: (v: View3d) => void;
|
||||||
|
fov: number;
|
||||||
|
onFov: (n: number) => void;
|
||||||
|
detail: DetailLevel;
|
||||||
|
onDetail: (d: DetailLevel) => void;
|
||||||
|
detailEnabled: boolean;
|
||||||
|
scaleDenominator: number;
|
||||||
|
onApplyScale: (n: number) => void;
|
||||||
|
liveScale: number | null;
|
||||||
|
zoomPercent: number;
|
||||||
|
zoomEnabled: boolean;
|
||||||
|
onFit: () => void;
|
||||||
|
onFitSelection: () => void;
|
||||||
|
onZoom100: () => void;
|
||||||
|
renderMode: RenderMode;
|
||||||
|
onRenderMode: (m: RenderMode) => void;
|
||||||
|
renderModeEnabled: boolean;
|
||||||
|
planColorMode: PlanColorMode;
|
||||||
|
onPlanColorMode: (m: PlanColorMode) => void;
|
||||||
|
combos: ComboMenuHandlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* „Ansichten"-Tab des Ribbons — trägt die aus der TopBar gemergten Mittelcluster:
|
||||||
|
* 2D/3D-Ansichts-Raster (+ Kamera-FOV), Sichtbarkeits-Kombinationen, Detailgrad,
|
||||||
|
* Massstab/Zoom-Cluster und Darstellungsart. EIN Ribbon für alles; Handler/Setter
|
||||||
|
* kommen unverändert von App (gleiche Props wie zuvor an die TopBar). Wird von
|
||||||
|
* `RibbonBar` als Inhalt des „Ansichten"-Tabs gerendert.
|
||||||
|
*/
|
||||||
|
export function ViewRibbonTab({
|
||||||
viewToggleEnabled,
|
viewToggleEnabled,
|
||||||
viewType,
|
viewType,
|
||||||
onViewType,
|
onViewType,
|
||||||
@@ -978,44 +963,13 @@ export function TopBar({
|
|||||||
onFit,
|
onFit,
|
||||||
onFitSelection,
|
onFitSelection,
|
||||||
onZoom100,
|
onZoom100,
|
||||||
onExportPdf,
|
|
||||||
onExportDxf,
|
|
||||||
onExportSchedule,
|
|
||||||
renderMode,
|
renderMode,
|
||||||
onRenderMode,
|
onRenderMode,
|
||||||
renderModeEnabled,
|
renderModeEnabled,
|
||||||
planColorMode,
|
planColorMode,
|
||||||
onPlanColorMode,
|
onPlanColorMode,
|
||||||
combos,
|
combos,
|
||||||
resourcesOpen,
|
}: ViewRibbonTabProps) {
|
||||||
onToggleResources,
|
|
||||||
onOpenSettings,
|
|
||||||
layoutMenu,
|
|
||||||
onImport,
|
|
||||||
onSaveProject,
|
|
||||||
onOpenProject,
|
|
||||||
textTarget,
|
|
||||||
}: TopBarProps) {
|
|
||||||
// Dezente Scrollleiste: die Oberleiste scrollt horizontal (overflow-x). Statt
|
|
||||||
// der prägnanten Standard-Leiste blenden wir eine eigene, sehr ruhige Leiste
|
|
||||||
// NUR während des Scrollens ein (Klasse `is-scrolling`, ~900 ms nachlaufend).
|
|
||||||
const headerRef = useRef<HTMLElement | null>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
const el = headerRef.current;
|
|
||||||
if (!el) return;
|
|
||||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
||||||
const onScroll = () => {
|
|
||||||
el.classList.add("is-scrolling");
|
|
||||||
if (timer) clearTimeout(timer);
|
|
||||||
timer = setTimeout(() => el.classList.remove("is-scrolling"), 900);
|
|
||||||
};
|
|
||||||
el.addEventListener("scroll", onScroll, { passive: true });
|
|
||||||
return () => {
|
|
||||||
el.removeEventListener("scroll", onScroll);
|
|
||||||
if (timer) clearTimeout(timer);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// „frei" wird über einen Prompt erfasst; akzeptiert „1:35" ODER „35".
|
// „frei" wird über einen Prompt erfasst; akzeptiert „1:35" ODER „35".
|
||||||
const onScaleChange = (value: string) => {
|
const onScaleChange = (value: string) => {
|
||||||
if (value === "__custom__") {
|
if (value === "__custom__") {
|
||||||
@@ -1030,14 +984,7 @@ export function TopBar({
|
|||||||
const n = Number(value);
|
const n = Number(value);
|
||||||
if (Number.isFinite(n) && n > 0) onApplyScale(n);
|
if (Number.isFinite(n) && n > 0) onApplyScale(n);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Aktueller Nenner steht evtl. nicht in den Presets (frei gesetzt) → dann
|
|
||||||
// einen Eintrag „1:N" voranstellen, damit das Select den Wert anzeigt.
|
|
||||||
const inPresets = SCALE_PRESETS.includes(scaleDenominator);
|
const inPresets = SCALE_PRESETS.includes(scaleDenominator);
|
||||||
|
|
||||||
// Optionsliste des Massstab-Dropdowns: ggf. der frei gesetzte aktuelle Wert
|
|
||||||
// (damit er sichtbar bleibt), dann die Presets, zuletzt „frei …" als
|
|
||||||
// Sonder-Wert, der über onScaleChange den Prompt öffnet (Funktion unverändert).
|
|
||||||
const scaleOptions: DropdownOption[] = [
|
const scaleOptions: DropdownOption[] = [
|
||||||
...(inPresets
|
...(inPresets
|
||||||
? []
|
? []
|
||||||
@@ -1047,41 +994,8 @@ export function TopBar({
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="topbar" ref={headerRef}>
|
<div className="ribbon-views">
|
||||||
{/* Marke (DOSSIER-Wortmarke) + Settings/Ressourcen-Icons daneben,
|
{/* Ansichten als EIN kompaktes Icon-Grid (2D-Grundriss + 3D-Blicke). */}
|
||||||
gestapelt wie im Rhino-Plugin (ToolbarApp.jsx: Wortmarke links,
|
|
||||||
rechts daneben ein schmaler Icon-Stapel). */}
|
|
||||||
<div className="tb-brandgroup">
|
|
||||||
<div className="brand">
|
|
||||||
<span className="brand-word">
|
|
||||||
DOSSIER<span className="brand-dot">.</span>
|
|
||||||
</span>
|
|
||||||
<span className="tag">{t("brand.tag")}</span>
|
|
||||||
</div>
|
|
||||||
<div className="tb-brandicons">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`tb-brandicon${resourcesOpen ? " active" : ""}`}
|
|
||||||
onClick={onToggleResources}
|
|
||||||
aria-pressed={resourcesOpen}
|
|
||||||
aria-label={t("topbar.resources")}
|
|
||||||
title={`${t("topbar.resources")} — ${t("topbar.resources.hint")}`}
|
|
||||||
>
|
|
||||||
<TbIcon name="widgets" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Sep />
|
|
||||||
|
|
||||||
{/* Ansichten als EIN kompaktes Icon-Grid (Vectorworks/DOSSIER-Stil): der
|
|
||||||
2D-Grundriss UND die 3D-Blicke sind eine durchgängige Icon-Serie —
|
|
||||||
„Grundriss" schaltet auf den 2D-Plan, die übrigen auf die jeweilige
|
|
||||||
3D-Ansicht (Perspektive wird dabei automatisch aktiviert). 4 oben /
|
|
||||||
3 unten. Tooltip nennt jede Ansicht im Klartext. */}
|
|
||||||
{/* Ansichts-Icons bleiben immer sichtbar; auf Nicht-Geschoss-Ebenen
|
|
||||||
(Schnitt/Ansicht/2D-Zeichnung) sind sie deaktiviert, weil dort keine
|
|
||||||
2D/3D-Umschaltung greift — die Serie verschwindet nicht mehr ganz. */}
|
|
||||||
<div className="tb-group" role="group" aria-label={t("topbar.viewGroup")}>
|
<div className="tb-group" role="group" aria-label={t("topbar.viewGroup")}>
|
||||||
<div className="view-grid view-grid-4">
|
<div className="view-grid view-grid-4">
|
||||||
<button
|
<button
|
||||||
@@ -1139,10 +1053,7 @@ export function TopBar({
|
|||||||
</div>
|
</div>
|
||||||
<Sep />
|
<Sep />
|
||||||
|
|
||||||
{/* Sichtbarkeits-Kombinationen (Vectorworks: Ebenen-/Zeichnungs-
|
{/* Sichtbarkeits-Kombinationen (Ebenen-/Zeichnungskombinationen). */}
|
||||||
kombinationen). Sitzt direkt nach dem Ansichts-Umschalter, weil beides
|
|
||||||
„Navigations-/Sicht"-Steuerung ist — ruhige Stelle vor dem Detailgrad.
|
|
||||||
Nur die Dropdowns; eine grafische Überschreibung kommt später. */}
|
|
||||||
<div className="tb-group tb-stack" role="group" aria-label={t("combo.layers.title")}>
|
<div className="tb-group tb-stack" role="group" aria-label={t("combo.layers.title")}>
|
||||||
<label className="tb-field tb-field-row">
|
<label className="tb-field tb-field-row">
|
||||||
<span className="tb-label">{t("combo.layers")}</span>
|
<span className="tb-label">{t("combo.layers")}</span>
|
||||||
@@ -1167,27 +1078,9 @@ export function TopBar({
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Sep />
|
<Sep />
|
||||||
|
|
||||||
{/* Text-Gruppe (immer sichtbar) — Stil/Schrift/Grösse + B/I/U · L/C/R +
|
{/* Detailgrad (grob/mittel/fein) — wirkt im Grundriss. */}
|
||||||
Zeilenhöhe. Formatiert das aktuelle Text-Ziel (Raumstempel) live.
|
|
||||||
Sitzt bewusst zwischen den Ebenen-Kombis und Detailgrad/Massstab.
|
|
||||||
„+ Text" ist raus — Text wird ein eigenständiges Zeichenwerkzeug
|
|
||||||
(HANDOVER-Backlog AUDIT B1), kein TopBar-Knopf mehr. */}
|
|
||||||
<TextGroup textTarget={textTarget} />
|
|
||||||
|
|
||||||
<Sep />
|
|
||||||
|
|
||||||
{/* Zeichenwerkzeuge liegen jetzt in der Werkzeug-Palette (ToolsPanel,
|
|
||||||
dockbar) — nicht mehr in der Oberleiste (Vectorworks-Bild: Werkzeuge
|
|
||||||
in der Palette). Auch Wandtyp-Wahl + Fang-Menü sind dort. */}
|
|
||||||
|
|
||||||
{/* Detailgrad (grob/mittel/fein) — wirkt im Grundriss auf Symbole +
|
|
||||||
Schraffuren/Linien. In der Perspektive (noch) ohne Wirkung → dort
|
|
||||||
deaktiviert mit Tooltip statt stiller No-Op. */}
|
|
||||||
{/* Detailgrad — Massstab lebt nur noch EINMAL im Zoom-Cluster (die
|
|
||||||
Dropdown hier war ein redundantes Relikt der TopBar-Reorg). */}
|
|
||||||
<div className="tb-group" role="group" aria-label={t("topbar.detailGroup")}>
|
<div className="tb-group" role="group" aria-label={t("topbar.detailGroup")}>
|
||||||
<label className="tb-field tb-field-row">
|
<label className="tb-field tb-field-row">
|
||||||
<span className="tb-label">{t("topbar.detail")}</span>
|
<span className="tb-label">{t("topbar.detail")}</span>
|
||||||
@@ -1208,14 +1101,9 @@ export function TopBar({
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Sep />
|
<Sep />
|
||||||
|
|
||||||
{/* Massstab/Zoom-Cluster (DOSSIER, 2×2). Links über beide Zeilen die
|
{/* Massstab/Zoom-Cluster (2×2). */}
|
||||||
kombinierte Stat-Pille (Live-Massstab 1:N oben / Zoom% unten); rechts
|
|
||||||
oben das Massstab-Dropdown, rechts unten die Zoom-Segmentpille. „am
|
|
||||||
Massstab" (Live-Massstab == gewählter Massstab) hebt die Stat-Pille im
|
|
||||||
Akzent hervor. */}
|
|
||||||
<div className="tb-group tb-zoomcluster" role="group" aria-label={t("topbar.scaleGroup")}>
|
<div className="tb-group tb-zoomcluster" role="group" aria-label={t("topbar.scaleGroup")}>
|
||||||
<div
|
<div
|
||||||
className={`tb-stat${
|
className={`tb-stat${
|
||||||
@@ -1271,13 +1159,9 @@ export function TopBar({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Sep />
|
<Sep />
|
||||||
|
|
||||||
{/* Darstellungsart (Vectorworks-Stil) — EIN Dropdown, dessen Optionen vom
|
{/* Darstellungsart — Grundriss: Farbig/SW; Perspektive: Render-Modus. */}
|
||||||
Ansichtstyp abhängen: Grundriss → Farbig/Schwarz-Weiss (planColorMode);
|
|
||||||
Perspektive → Schattiert/Weiss/Drahtgitter/Kanten (renderMode). Der
|
|
||||||
Linien-Modus (Display/Print) bleibt separat. */}
|
|
||||||
<div className="tb-group" role="group" aria-label={t("display.style.group")}>
|
<div className="tb-group" role="group" aria-label={t("display.style.group")}>
|
||||||
<label className="tb-field">
|
<label className="tb-field">
|
||||||
<span className="tb-label">{t("display.style")}</span>
|
<span className="tb-label">{t("display.style")}</span>
|
||||||
@@ -1308,9 +1192,79 @@ export function TopBar({
|
|||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TopBar({
|
||||||
|
project,
|
||||||
|
zoomEnabled,
|
||||||
|
onExportPdf,
|
||||||
|
onExportDxf,
|
||||||
|
onExportSchedule,
|
||||||
|
resourcesOpen,
|
||||||
|
onToggleResources,
|
||||||
|
onOpenSettings,
|
||||||
|
layoutMenu,
|
||||||
|
onImport,
|
||||||
|
onSaveProject,
|
||||||
|
onOpenProject,
|
||||||
|
textTarget,
|
||||||
|
}: TopBarProps) {
|
||||||
|
// Dezente Scrollleiste: die Oberleiste scrollt horizontal (overflow-x). Statt
|
||||||
|
// der prägnanten Standard-Leiste blenden wir eine eigene, sehr ruhige Leiste
|
||||||
|
// NUR während des Scrollens ein (Klasse `is-scrolling`, ~900 ms nachlaufend).
|
||||||
|
const headerRef = useRef<HTMLElement | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
const el = headerRef.current;
|
||||||
|
if (!el) return;
|
||||||
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
const onScroll = () => {
|
||||||
|
el.classList.add("is-scrolling");
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
timer = setTimeout(() => el.classList.remove("is-scrolling"), 900);
|
||||||
|
};
|
||||||
|
el.addEventListener("scroll", onScroll, { passive: true });
|
||||||
|
return () => {
|
||||||
|
el.removeEventListener("scroll", onScroll);
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className="topbar" ref={headerRef}>
|
||||||
|
{/* Marke (DOSSIER-Wortmarke) + Settings/Ressourcen-Icons daneben,
|
||||||
|
gestapelt wie im Rhino-Plugin (ToolbarApp.jsx: Wortmarke links,
|
||||||
|
rechts daneben ein schmaler Icon-Stapel). */}
|
||||||
|
<div className="tb-brandgroup">
|
||||||
|
<div className="brand">
|
||||||
|
<span className="brand-word">
|
||||||
|
DOSSIER<span className="brand-dot">.</span>
|
||||||
|
</span>
|
||||||
|
<span className="tag">{t("brand.tag")}</span>
|
||||||
|
</div>
|
||||||
|
<div className="tb-brandicons">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`tb-brandicon${resourcesOpen ? " active" : ""}`}
|
||||||
|
onClick={onToggleResources}
|
||||||
|
aria-pressed={resourcesOpen}
|
||||||
|
aria-label={t("topbar.resources")}
|
||||||
|
title={`${t("topbar.resources")} — ${t("topbar.resources.hint")}`}
|
||||||
|
>
|
||||||
|
<TbIcon name="widgets" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Sep />
|
<Sep />
|
||||||
|
|
||||||
|
{/* Text-Gruppe (kontextabhängig) — Stil/Schrift/Grösse + B/I/U · L/C/R +
|
||||||
|
Zeilenhöhe. Formatiert das aktuelle Text-Ziel (Raumstempel) live. Bleibt
|
||||||
|
in der schmalen globalen Leiste, weil sie eine kontextuelle Formatier-
|
||||||
|
Steuerung ist (kein Ansichts-/Ausgabe-Element). */}
|
||||||
|
<TextGroup textTarget={textTarget} />
|
||||||
|
|
||||||
{/* Rechte Gruppe: Layout-Menü · Projektname · Datei-Burger (ganz rechts,
|
{/* Rechte Gruppe: Layout-Menü · Projektname · Datei-Burger (ganz rechts,
|
||||||
GTK-Stil) — Ressourcen sitzt jetzt bei der Marke (s. o.). */}
|
GTK-Stil) — Ressourcen sitzt jetzt bei der Marke (s. o.). */}
|
||||||
<div className="topbar-right">
|
<div className="topbar-right">
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
// Bezeichner englisch, UI-Text/Kommentare deutsch (CONVENTIONS.md).
|
// Bezeichner englisch, UI-Text/Kommentare deutsch (CONVENTIONS.md).
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
import { t } from "../../i18n";
|
import { t } from "../../i18n";
|
||||||
import { ToolIcon } from "../../panels/ToolsPanel";
|
import { ToolIcon } from "../../panels/ToolsPanel";
|
||||||
import { getTool } from "../../tools/tools";
|
import { getTool } from "../../tools/tools";
|
||||||
@@ -32,6 +33,13 @@ export interface RibbonBarProps {
|
|||||||
onSelectTool: (id: ToolId) => void;
|
onSelectTool: (id: ToolId) => void;
|
||||||
/** Engine-Befehl starten (Ändern-Gruppe u. a.). */
|
/** Engine-Befehl starten (Ändern-Gruppe u. a.). */
|
||||||
onRunCommand: (name: string) => void;
|
onRunCommand: (name: string) => void;
|
||||||
|
/**
|
||||||
|
* Reicher Inhalt des „Ansichten"-Tabs (aus der TopBar gemergte Ansichts-/Zoom-/
|
||||||
|
* Darstellungs-Steuerung, `ViewRibbonTab`). Von App geliefert, damit dessen
|
||||||
|
* Setter/State dort bleiben — analog dazu, wie die TopBar das Layout-Menü als
|
||||||
|
* Node bekommt. Fehlt er, zeigt der Tab den Leer-Hinweis.
|
||||||
|
*/
|
||||||
|
viewsContent?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ein einzelner Ribbon-Button (Werkzeug ODER Befehl). */
|
/** Ein einzelner Ribbon-Button (Werkzeug ODER Befehl). */
|
||||||
@@ -104,9 +112,12 @@ export function RibbonBar(props: RibbonBarProps) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Gruppen des aktiven Tabs */}
|
{/* Gruppen des aktiven Tabs. Der „Ansichten"-Tab trägt reichen Custom-
|
||||||
|
Inhalt (ViewRibbonTab) statt der datengetriebenen Item-Gruppen. */}
|
||||||
<div className="ribbon-body">
|
<div className="ribbon-body">
|
||||||
{current.groups.length === 0 ? (
|
{current.id === "views" && props.viewsContent ? (
|
||||||
|
props.viewsContent
|
||||||
|
) : current.groups.length === 0 ? (
|
||||||
<div className="ribbon-empty">{t("ribbon.empty")}</div>
|
<div className="ribbon-empty">{t("ribbon.empty")}</div>
|
||||||
) : (
|
) : (
|
||||||
current.groups.map((g) => (
|
current.groups.map((g) => (
|
||||||
|
|||||||
@@ -41,9 +41,10 @@ const t = (id: ToolId): RibbonItem => ({ kind: "tool", id });
|
|||||||
const c = (name: string): RibbonItem => ({ kind: "command", name });
|
const c = (name: string): RibbonItem => ({ kind: "command", name });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Ribbon-Tabs. Phase 1 füllt 2D (Zeichnen + Ändern) und BIM (Bauteile) mit
|
* Die Ribbon-Tabs (Reihenfolge 2D·3D·BIM·Ansichten). 2D (Zeichnen + Ändern) und
|
||||||
* den vorhandenen Werkzeugen/Befehlen; 3D und Ansichten bleiben vorerst leer
|
* BIM (Bauteile) sind datengetrieben aus Werkzeugen/Befehlen; „Ansichten" trägt
|
||||||
* (Platzhalter, in einer späteren Phase gefüllt — siehe Plan-Doc).
|
* reichen Custom-Inhalt (aus der TopBar gemergt, siehe `ViewRibbonTab` — die
|
||||||
|
* Registry lässt seine `groups` leer). 3D ist vorerst leer (spätere Phase).
|
||||||
*/
|
*/
|
||||||
export const RIBBON_TABS: RibbonTab[] = [
|
export const RIBBON_TABS: RibbonTab[] = [
|
||||||
{
|
{
|
||||||
@@ -74,6 +75,11 @@ export const RIBBON_TABS: RibbonTab[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "3d",
|
||||||
|
labelKey: "ribbon.tab.3d",
|
||||||
|
groups: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "bim",
|
id: "bim",
|
||||||
labelKey: "ribbon.tab.bim",
|
labelKey: "ribbon.tab.bim",
|
||||||
@@ -91,11 +97,6 @@ export const RIBBON_TABS: RibbonTab[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "3d",
|
|
||||||
labelKey: "ribbon.tab.3d",
|
|
||||||
groups: [],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "views",
|
id: "views",
|
||||||
labelKey: "ribbon.tab.views",
|
labelKey: "ribbon.tab.views",
|
||||||
|
|||||||
Reference in New Issue
Block a user