Kopfzeile der Zeichenflaeche: Dokument-Tabs statt Geschoss-Eigenschaften
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.
This commit is contained in:
+37
-52
@@ -156,6 +156,8 @@ import {
|
|||||||
} from "./state/visibilitySets";
|
} from "./state/visibilitySets";
|
||||||
// Seiteneffekt: registriert die zwei dockbaren Kern-Panels in der Registry.
|
// Seiteneffekt: registriert die zwei dockbaren Kern-Panels in der Registry.
|
||||||
import { BUILTIN_PANEL_IDS } from "./panels/builtinPanels";
|
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).
|
* 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.
|
// Panels/Menüs/Editoren weiter.
|
||||||
const toggleLevelVisible = useStore((s) => s.toggleLevelVisible);
|
const toggleLevelVisible = useStore((s) => s.toggleLevelVisible);
|
||||||
const toggleCategoryVisible = useStore((s) => s.toggleCategoryVisible);
|
const toggleCategoryVisible = useStore((s) => s.toggleCategoryVisible);
|
||||||
const patchActiveFloor = useStore((s) => s.patchActiveFloor);
|
|
||||||
const addFloor = useStore((s) => s.addFloor);
|
const addFloor = useStore((s) => s.addFloor);
|
||||||
const addDrawing = useStore((s) => s.addDrawing);
|
const addDrawing = useStore((s) => s.addDrawing);
|
||||||
const addDrawingLevel = useStore((s) => s.addDrawingLevel);
|
const addDrawingLevel = useStore((s) => s.addDrawingLevel);
|
||||||
@@ -2195,6 +2196,14 @@ export default function App() {
|
|||||||
// referenzstabil → triggert sonst keinen Neuaufbau).
|
// referenzstabil → triggert sonst keinen Neuaufbau).
|
||||||
const displayKey = `${drawingLevelsMode}|${layersMode}|${activeLevelId}`;
|
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 ────────────────────────────
|
// ── Ableitungen für Oberleiste + Status-Leiste ────────────────────────────
|
||||||
// Eine zoom-/cursor-fähige Plan-Ansicht ist nur im Grundriss eines Geschosses
|
// Eine zoom-/cursor-fähige Plan-Ansicht ist nur im Grundriss eines Geschosses
|
||||||
// aktiv. Sonst sind Cursor/Zoom bedeutungslos (PlanView ist nicht montiert →
|
// aktiv. Sonst sind Cursor/Zoom bedeutungslos (PlanView ist nicht montiert →
|
||||||
@@ -2794,7 +2803,8 @@ export default function App() {
|
|||||||
view3d={view3d}
|
view3d={view3d}
|
||||||
fov={fov}
|
fov={fov}
|
||||||
mono={planColorMode === "mono"}
|
mono={planColorMode === "mono"}
|
||||||
onPatchFloor={patchActiveFloor}
|
drawings={drawings}
|
||||||
|
onSelectLevel={setActiveLevelId}
|
||||||
floorDisplay={floorDisplay}
|
floorDisplay={floorDisplay}
|
||||||
categoryDisplay={categoryDisplay}
|
categoryDisplay={categoryDisplay}
|
||||||
displayKey={displayKey}
|
displayKey={displayKey}
|
||||||
@@ -3732,7 +3742,8 @@ function Content({
|
|||||||
view3d,
|
view3d,
|
||||||
fov,
|
fov,
|
||||||
mono,
|
mono,
|
||||||
onPatchFloor,
|
drawings,
|
||||||
|
onSelectLevel,
|
||||||
floorDisplay,
|
floorDisplay,
|
||||||
categoryDisplay,
|
categoryDisplay,
|
||||||
displayKey,
|
displayKey,
|
||||||
@@ -3790,7 +3801,11 @@ function Content({
|
|||||||
fov: number;
|
fov: number;
|
||||||
/** Grundriss in Schwarz-Weiss (mono) statt mit Kategorie-/Element-Farben. */
|
/** Grundriss in Schwarz-Weiss (mono) statt mit Kategorie-/Element-Farben. */
|
||||||
mono: boolean;
|
mono: boolean;
|
||||||
onPatchFloor: (patch: Partial<DrawingLevel>) => 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;
|
floorDisplay: DisplayResolver;
|
||||||
categoryDisplay: CategoryDisplayResolver & DisplayResolver;
|
categoryDisplay: CategoryDisplayResolver & DisplayResolver;
|
||||||
displayKey: string;
|
displayKey: string;
|
||||||
@@ -3853,6 +3868,11 @@ function Content({
|
|||||||
if (level.kind === "section" || level.kind === "elevation") {
|
if (level.kind === "section" || level.kind === "elevation") {
|
||||||
return (
|
return (
|
||||||
<main className="content">
|
<main className="content">
|
||||||
|
<DrawingTabs
|
||||||
|
drawings={drawings}
|
||||||
|
activeLevelId={level.id}
|
||||||
|
onSelectLevel={onSelectLevel}
|
||||||
|
/>
|
||||||
<SectionPlanView
|
<SectionPlanView
|
||||||
project={project}
|
project={project}
|
||||||
level={level}
|
level={level}
|
||||||
@@ -3894,6 +3914,11 @@ function Content({
|
|||||||
if (level.kind === "drawing") {
|
if (level.kind === "drawing") {
|
||||||
return (
|
return (
|
||||||
<main className="content">
|
<main className="content">
|
||||||
|
<DrawingTabs
|
||||||
|
drawings={drawings}
|
||||||
|
activeLevelId={level.id}
|
||||||
|
onSelectLevel={onSelectLevel}
|
||||||
|
/>
|
||||||
<LevelPlanView
|
<LevelPlanView
|
||||||
project={project}
|
project={project}
|
||||||
level={level}
|
level={level}
|
||||||
@@ -3932,10 +3957,16 @@ function Content({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Geschoss: Eigenschaften-Leiste + gewählte Ansicht.
|
// Geschoss: Dokument-Tabs + gewählte Ansicht. Geschosshöhe/Schnitthöhe
|
||||||
|
// werden in den Geschoss-Einstellungen gepflegt (Rechtsklick auf das
|
||||||
|
// Geschoss im Panel „Zeichnungsebenen"), nicht mehr hier in der Kopfzeile.
|
||||||
return (
|
return (
|
||||||
<main className="content">
|
<main className="content">
|
||||||
<FloorProps level={level} onPatch={onPatchFloor} />
|
<DrawingTabs
|
||||||
|
drawings={drawings}
|
||||||
|
activeLevelId={level.id}
|
||||||
|
onSelectLevel={onSelectLevel}
|
||||||
|
/>
|
||||||
{viewType === "perspektive" ? (
|
{viewType === "perspektive" ? (
|
||||||
<section className="pane">
|
<section className="pane">
|
||||||
<div className="pane-title">
|
<div className="pane-title">
|
||||||
@@ -4008,52 +4039,6 @@ function Content({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Kompakte Eigenschaften-Leiste eines Geschosses. */
|
|
||||||
function FloorProps({
|
|
||||||
level,
|
|
||||||
onPatch,
|
|
||||||
}: {
|
|
||||||
level: DrawingLevel;
|
|
||||||
onPatch: (patch: Partial<DrawingLevel>) => void;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="props-strip">
|
|
||||||
<label className="prop">
|
|
||||||
<span className="prop-label">{t("props.floorHeight")}</span>
|
|
||||||
<span className="prop-input">
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
step={0.05}
|
|
||||||
min={0}
|
|
||||||
value={level.floorHeight ?? 0}
|
|
||||||
onChange={(e) => onPatch({ floorHeight: Number(e.target.value) })}
|
|
||||||
/>
|
|
||||||
<span className="prop-unit">{t("editor.unit.m")}</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<label className="prop">
|
|
||||||
<span className="prop-label">{t("props.cutHeight")}</span>
|
|
||||||
<span className="prop-input">
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
step={0.05}
|
|
||||||
min={0}
|
|
||||||
value={level.cutHeight ?? 0}
|
|
||||||
onChange={(e) => onPatch({ cutHeight: Number(e.target.value) })}
|
|
||||||
/>
|
|
||||||
<span className="prop-unit">{t("editor.unit.m")}</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<span className="prop prop-readonly">
|
|
||||||
<span className="prop-label">{t("props.okff")}</span>
|
|
||||||
<span className="prop-value">
|
|
||||||
{t("props.okffValue", { value: (level.baseElevation ?? 0).toFixed(2) })}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Abgeleiteter Grundriss eines Geschosses. */
|
/** Abgeleiteter Grundriss eines Geschosses. */
|
||||||
function LevelPlanView({
|
function LevelPlanView({
|
||||||
project,
|
project,
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<div className="drawing-tabs" role="tablist" aria-label={t("tabs.drawings")}>
|
||||||
|
{drawings.map((d) => (
|
||||||
|
<button
|
||||||
|
key={d.id}
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={d.levelId === activeLevelId}
|
||||||
|
className={`drawing-tab${d.levelId === activeLevelId ? " active" : ""}`}
|
||||||
|
onClick={() => activate(d)}
|
||||||
|
title={d.title}
|
||||||
|
>
|
||||||
|
<span className="drawing-tab-label">{d.title}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -191,6 +191,9 @@ export const de = {
|
|||||||
"status.rendererWasm": "Engine",
|
"status.rendererWasm": "Engine",
|
||||||
"status.rendererWasmUnavailable": "WebGPU nicht verfügbar",
|
"status.rendererWasmUnavailable": "WebGPU nicht verfügbar",
|
||||||
|
|
||||||
|
// ── Dokument-Tabs (Zeichnungen/Blätter) ─────────────────────────────────
|
||||||
|
"tabs.drawings": "Zeichnungen",
|
||||||
|
|
||||||
// ── Navigator-Panels ─────────────────────────────────────────────────────
|
// ── Navigator-Panels ─────────────────────────────────────────────────────
|
||||||
"nav.drawingLevels": "Zeichnungsebenen",
|
"nav.drawingLevels": "Zeichnungsebenen",
|
||||||
"nav.layers": "Ebenen",
|
"nav.layers": "Ebenen",
|
||||||
|
|||||||
@@ -190,6 +190,9 @@ export const en: Record<TranslationKey, string> = {
|
|||||||
"status.rendererWasm": "Engine",
|
"status.rendererWasm": "Engine",
|
||||||
"status.rendererWasmUnavailable": "WebGPU unavailable",
|
"status.rendererWasmUnavailable": "WebGPU unavailable",
|
||||||
|
|
||||||
|
// Document tabs (drawings/sheets).
|
||||||
|
"tabs.drawings": "Drawings",
|
||||||
|
|
||||||
// Navigator panels.
|
// Navigator panels.
|
||||||
"nav.drawingLevels": "Drawing levels",
|
"nav.drawingLevels": "Drawing levels",
|
||||||
"nav.layers": "Layers",
|
"nav.layers": "Layers",
|
||||||
|
|||||||
+34
-50
@@ -1306,66 +1306,50 @@ body {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Eigenschaften-Leiste eines Geschosses. */
|
/* Dokument-Tabs „Zeichnungen/Blätter" — Kopfzeile über der Zeichenfläche.
|
||||||
.props-strip {
|
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;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-end;
|
||||||
gap: 18px;
|
gap: 2px;
|
||||||
padding: 8px 16px;
|
padding: 6px 10px 0;
|
||||||
border-bottom: 1px solid var(--line);
|
border-bottom: 1px solid var(--line);
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
|
overflow-x: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prop {
|
.drawing-tab {
|
||||||
display: inline-flex;
|
border: 1px solid transparent;
|
||||||
align-items: center;
|
border-bottom: none;
|
||||||
gap: 8px;
|
background: transparent;
|
||||||
}
|
color: var(--muted);
|
||||||
|
font-family: inherit;
|
||||||
.prop-label {
|
font-size: 12px;
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.01em;
|
||||||
text-transform: uppercase;
|
padding: 7px 16px;
|
||||||
color: var(--muted);
|
border-radius: 8px 8px 0 0;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: background 0.14s, color 0.14s, border-color 0.14s;
|
||||||
}
|
}
|
||||||
|
.drawing-tab:hover {
|
||||||
.prop-input {
|
background: var(--panel-2);
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prop-input input {
|
|
||||||
width: 64px;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--ink);
|
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 {
|
.drawing-tab.active {
|
||||||
border-color: var(--accent);
|
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 {
|
.drawing-tab-label {
|
||||||
border-color: var(--accent);
|
pointer-events: none;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Eine Ansicht füllt die Restfläche. */
|
/* Eine Ansicht füllt die Restfläche. */
|
||||||
|
|||||||
Reference in New Issue
Block a user