Ribbon: TopBar-Ansichtssteuerung in Ansichten-Tab gemergt; TopBar auf schmale globale Leiste reduziert
This commit is contained in:
+55
-47
@@ -69,7 +69,7 @@ import type { Pick3dHit } from "./viewport/Wasm3DViewport";
|
||||
import { ResourceManager } from "./ui/ResourceManager";
|
||||
import type { ResourceManagerHandlers } from "./ui/ResourceManager";
|
||||
import { HatchSwatch } from "./ui/hatchPreview";
|
||||
import { TopBar } from "./ui/TopBar";
|
||||
import { TopBar, ViewRibbonTab } from "./ui/TopBar";
|
||||
import { RibbonBar } from "./ui/ribbon/RibbonBar";
|
||||
import { Dropdown } from "./ui/Dropdown";
|
||||
import type { DropdownItem } from "./ui/Dropdown";
|
||||
@@ -3191,55 +3191,15 @@ export default function App() {
|
||||
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
|
||||
project={project}
|
||||
viewToggleEnabled={activeLevel.kind === "floor"}
|
||||
viewType={viewType}
|
||||
onViewType={setViewType}
|
||||
view3d={view3d}
|
||||
onView3d={setView3d}
|
||||
fov={fov}
|
||||
onFov={setFov}
|
||||
detail={detail}
|
||||
onDetail={setDetail}
|
||||
detailEnabled={planActive}
|
||||
scaleDenominator={scaleDenominator}
|
||||
onApplyScale={applyScale}
|
||||
liveScale={planActive ? liveScale : null}
|
||||
zoomPercent={zoomPercent}
|
||||
zoomEnabled={planActive}
|
||||
onFit={fitPlan}
|
||||
onFitSelection={fitSelection}
|
||||
onZoom100={zoomToApplied}
|
||||
onExportPdf={() => setExportOpen(true)}
|
||||
onExportDxf={() => setExportDxfOpen(true)}
|
||||
onExportSchedule={onExportSchedule}
|
||||
renderMode={renderMode}
|
||||
onRenderMode={setRenderMode}
|
||||
renderModeEnabled={viewType === "perspektive"}
|
||||
planColorMode={planColorMode}
|
||||
onPlanColorMode={setPlanColorMode}
|
||||
combos={{
|
||||
// Ebenen-Kombinationen: Speichern = Snapshot des Layer-Baums →
|
||||
// localStorage; Laden = Map lesen → Sichtbarkeits-Flags anwenden.
|
||||
listLayerCombos,
|
||||
saveLayerCombo: (name) =>
|
||||
saveLayerCombo(name, snapshotLayerVisibility()),
|
||||
loadLayerCombo: (name) => {
|
||||
const codes = loadLayerCombo(name);
|
||||
if (codes) applyLayerVisibility(codes);
|
||||
},
|
||||
deleteLayerCombo,
|
||||
// Zeichnungs-Kombinationen: analog über drawingLevels[].visible.
|
||||
listDrawingCombos,
|
||||
saveDrawingCombo: (name) =>
|
||||
saveDrawingCombo(name, snapshotDrawingVisibility()),
|
||||
loadDrawingCombo: (name) => {
|
||||
const ids = loadDrawingCombo(name);
|
||||
if (ids) applyDrawingVisibility(ids);
|
||||
},
|
||||
deleteDrawingCombo,
|
||||
}}
|
||||
resourcesOpen={resourcesOpen}
|
||||
onToggleResources={() => setResourcesOpen((v) => !v)}
|
||||
onOpenSettings={() => setSettingsOpen(true)}
|
||||
@@ -3255,15 +3215,63 @@ export default function App() {
|
||||
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). */}
|
||||
{/* 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"}
|
||||
viewType={viewType}
|
||||
onViewType={setViewType}
|
||||
view3d={view3d}
|
||||
onView3d={setView3d}
|
||||
fov={fov}
|
||||
onFov={setFov}
|
||||
detail={detail}
|
||||
onDetail={setDetail}
|
||||
detailEnabled={planActive}
|
||||
scaleDenominator={scaleDenominator}
|
||||
onApplyScale={applyScale}
|
||||
liveScale={planActive ? liveScale : null}
|
||||
zoomPercent={zoomPercent}
|
||||
zoomEnabled={planActive}
|
||||
onFit={fitPlan}
|
||||
onFitSelection={fitSelection}
|
||||
onZoom100={zoomToApplied}
|
||||
renderMode={renderMode}
|
||||
onRenderMode={setRenderMode}
|
||||
renderModeEnabled={viewType === "perspektive"}
|
||||
planColorMode={planColorMode}
|
||||
onPlanColorMode={setPlanColorMode}
|
||||
combos={{
|
||||
// Ebenen-Kombinationen: Speichern = Snapshot des Layer-Baums →
|
||||
// localStorage; Laden = Map lesen → Sichtbarkeits-Flags anwenden.
|
||||
listLayerCombos,
|
||||
saveLayerCombo: (name) =>
|
||||
saveLayerCombo(name, snapshotLayerVisibility()),
|
||||
loadLayerCombo: (name) => {
|
||||
const codes = loadLayerCombo(name);
|
||||
if (codes) applyLayerVisibility(codes);
|
||||
},
|
||||
deleteLayerCombo,
|
||||
// Zeichnungs-Kombinationen: analog über drawingLevels[].visible.
|
||||
listDrawingCombos,
|
||||
saveDrawingCombo: (name) =>
|
||||
saveDrawingCombo(name, snapshotDrawingVisibility()),
|
||||
loadDrawingCombo: (name) => {
|
||||
const ids = loadDrawingCombo(name);
|
||||
if (ids) applyDrawingVisibility(ids);
|
||||
},
|
||||
deleteDrawingCombo,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="body">
|
||||
|
||||
@@ -5472,3 +5472,12 @@ body {
|
||||
font-style: italic;
|
||||
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 {
|
||||
project: Project;
|
||||
|
||||
// Ansicht.
|
||||
/** Ob der Ansichts-Umschalter sinnvoll ist (nur für Geschosse). */
|
||||
viewToggleEnabled: boolean;
|
||||
viewType: ViewType;
|
||||
onViewType: (v: ViewType) => void;
|
||||
// Ansichts-/Zoom-/Darstellungs-Steuerung ist in den „Ansichten"-Ribbon-Tab
|
||||
// gewandert (siehe ViewRibbonTab). Die TopBar ist nur noch die schmale globale
|
||||
// Leiste: Marke/Ressourcen · Text-Formatierung · Datei/Export/Einstellungen.
|
||||
|
||||
// 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. */
|
||||
/** Ob Zoom/Plan aktiv ist — steuert nur noch, ob Export im Datei-Menü geht. */
|
||||
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). */
|
||||
@@ -134,21 +97,6 @@ export interface TopBarProps {
|
||||
/** Lädt die Bauteilliste des Projekts als CSV herunter. */
|
||||
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.
|
||||
resourcesOpen: boolean;
|
||||
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,
|
||||
viewType,
|
||||
onViewType,
|
||||
@@ -978,44 +963,13 @@ export function TopBar({
|
||||
onFit,
|
||||
onFitSelection,
|
||||
onZoom100,
|
||||
onExportPdf,
|
||||
onExportDxf,
|
||||
onExportSchedule,
|
||||
renderMode,
|
||||
onRenderMode,
|
||||
renderModeEnabled,
|
||||
planColorMode,
|
||||
onPlanColorMode,
|
||||
combos,
|
||||
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);
|
||||
};
|
||||
}, []);
|
||||
|
||||
}: ViewRibbonTabProps) {
|
||||
// „frei" wird über einen Prompt erfasst; akzeptiert „1:35" ODER „35".
|
||||
const onScaleChange = (value: string) => {
|
||||
if (value === "__custom__") {
|
||||
@@ -1030,14 +984,7 @@ export function TopBar({
|
||||
const n = Number(value);
|
||||
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);
|
||||
|
||||
// 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[] = [
|
||||
...(inPresets
|
||||
? []
|
||||
@@ -1047,41 +994,8 @@ export function TopBar({
|
||||
];
|
||||
|
||||
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 />
|
||||
|
||||
{/* 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="ribbon-views">
|
||||
{/* Ansichten als EIN kompaktes Icon-Grid (2D-Grundriss + 3D-Blicke). */}
|
||||
<div className="tb-group" role="group" aria-label={t("topbar.viewGroup")}>
|
||||
<div className="view-grid view-grid-4">
|
||||
<button
|
||||
@@ -1139,10 +1053,7 @@ export function TopBar({
|
||||
</div>
|
||||
<Sep />
|
||||
|
||||
{/* Sichtbarkeits-Kombinationen (Vectorworks: Ebenen-/Zeichnungs-
|
||||
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. */}
|
||||
{/* Sichtbarkeits-Kombinationen (Ebenen-/Zeichnungskombinationen). */}
|
||||
<div className="tb-group tb-stack" role="group" aria-label={t("combo.layers.title")}>
|
||||
<label className="tb-field tb-field-row">
|
||||
<span className="tb-label">{t("combo.layers")}</span>
|
||||
@@ -1167,27 +1078,9 @@ export function TopBar({
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Sep />
|
||||
|
||||
{/* Text-Gruppe (immer sichtbar) — Stil/Schrift/Grösse + B/I/U · L/C/R +
|
||||
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). */}
|
||||
{/* Detailgrad (grob/mittel/fein) — wirkt im Grundriss. */}
|
||||
<div className="tb-group" role="group" aria-label={t("topbar.detailGroup")}>
|
||||
<label className="tb-field tb-field-row">
|
||||
<span className="tb-label">{t("topbar.detail")}</span>
|
||||
@@ -1208,14 +1101,9 @@ export function TopBar({
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Sep />
|
||||
|
||||
{/* Massstab/Zoom-Cluster (DOSSIER, 2×2). Links über beide Zeilen die
|
||||
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. */}
|
||||
{/* Massstab/Zoom-Cluster (2×2). */}
|
||||
<div className="tb-group tb-zoomcluster" role="group" aria-label={t("topbar.scaleGroup")}>
|
||||
<div
|
||||
className={`tb-stat${
|
||||
@@ -1271,13 +1159,9 @@ export function TopBar({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Sep />
|
||||
|
||||
{/* Darstellungsart (Vectorworks-Stil) — EIN Dropdown, dessen Optionen vom
|
||||
Ansichtstyp abhängen: Grundriss → Farbig/Schwarz-Weiss (planColorMode);
|
||||
Perspektive → Schattiert/Weiss/Drahtgitter/Kanten (renderMode). Der
|
||||
Linien-Modus (Display/Print) bleibt separat. */}
|
||||
{/* Darstellungsart — Grundriss: Farbig/SW; Perspektive: Render-Modus. */}
|
||||
<div className="tb-group" role="group" aria-label={t("display.style.group")}>
|
||||
<label className="tb-field">
|
||||
<span className="tb-label">{t("display.style")}</span>
|
||||
@@ -1308,9 +1192,79 @@ export function TopBar({
|
||||
)}
|
||||
</label>
|
||||
</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 />
|
||||
|
||||
{/* 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,
|
||||
GTK-Stil) — Ressourcen sitzt jetzt bei der Marke (s. o.). */}
|
||||
<div className="topbar-right">
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// Bezeichner englisch, UI-Text/Kommentare deutsch (CONVENTIONS.md).
|
||||
|
||||
import { useState } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { t } from "../../i18n";
|
||||
import { ToolIcon } from "../../panels/ToolsPanel";
|
||||
import { getTool } from "../../tools/tools";
|
||||
@@ -32,6 +33,13 @@ export interface RibbonBarProps {
|
||||
onSelectTool: (id: ToolId) => void;
|
||||
/** Engine-Befehl starten (Ändern-Gruppe u. a.). */
|
||||
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). */
|
||||
@@ -104,9 +112,12 @@ export function RibbonBar(props: RibbonBarProps) {
|
||||
))}
|
||||
</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">
|
||||
{current.groups.length === 0 ? (
|
||||
{current.id === "views" && props.viewsContent ? (
|
||||
props.viewsContent
|
||||
) : current.groups.length === 0 ? (
|
||||
<div className="ribbon-empty">{t("ribbon.empty")}</div>
|
||||
) : (
|
||||
current.groups.map((g) => (
|
||||
|
||||
@@ -41,9 +41,10 @@ const t = (id: ToolId): RibbonItem => ({ kind: "tool", id });
|
||||
const c = (name: string): RibbonItem => ({ kind: "command", name });
|
||||
|
||||
/**
|
||||
* Die Ribbon-Tabs. Phase 1 füllt 2D (Zeichnen + Ändern) und BIM (Bauteile) mit
|
||||
* den vorhandenen Werkzeugen/Befehlen; 3D und Ansichten bleiben vorerst leer
|
||||
* (Platzhalter, in einer späteren Phase gefüllt — siehe Plan-Doc).
|
||||
* Die Ribbon-Tabs (Reihenfolge 2D·3D·BIM·Ansichten). 2D (Zeichnen + Ändern) und
|
||||
* BIM (Bauteile) sind datengetrieben aus Werkzeugen/Befehlen; „Ansichten" trägt
|
||||
* 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[] = [
|
||||
{
|
||||
@@ -74,6 +75,11 @@ export const RIBBON_TABS: RibbonTab[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "3d",
|
||||
labelKey: "ribbon.tab.3d",
|
||||
groups: [],
|
||||
},
|
||||
{
|
||||
id: "bim",
|
||||
labelKey: "ribbon.tab.bim",
|
||||
@@ -91,11 +97,6 @@ export const RIBBON_TABS: RibbonTab[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "3d",
|
||||
labelKey: "ribbon.tab.3d",
|
||||
groups: [],
|
||||
},
|
||||
{
|
||||
id: "views",
|
||||
labelKey: "ribbon.tab.views",
|
||||
|
||||
Reference in New Issue
Block a user