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";
|
||||
// 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<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;
|
||||
categoryDisplay: CategoryDisplayResolver & DisplayResolver;
|
||||
displayKey: string;
|
||||
@@ -3853,6 +3868,11 @@ function Content({
|
||||
if (level.kind === "section" || level.kind === "elevation") {
|
||||
return (
|
||||
<main className="content">
|
||||
<DrawingTabs
|
||||
drawings={drawings}
|
||||
activeLevelId={level.id}
|
||||
onSelectLevel={onSelectLevel}
|
||||
/>
|
||||
<SectionPlanView
|
||||
project={project}
|
||||
level={level}
|
||||
@@ -3894,6 +3914,11 @@ function Content({
|
||||
if (level.kind === "drawing") {
|
||||
return (
|
||||
<main className="content">
|
||||
<DrawingTabs
|
||||
drawings={drawings}
|
||||
activeLevelId={level.id}
|
||||
onSelectLevel={onSelectLevel}
|
||||
/>
|
||||
<LevelPlanView
|
||||
project={project}
|
||||
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 (
|
||||
<main className="content">
|
||||
<FloorProps level={level} onPatch={onPatchFloor} />
|
||||
<DrawingTabs
|
||||
drawings={drawings}
|
||||
activeLevelId={level.id}
|
||||
onSelectLevel={onSelectLevel}
|
||||
/>
|
||||
{viewType === "perspektive" ? (
|
||||
<section className="pane">
|
||||
<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. */
|
||||
function LevelPlanView({
|
||||
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.rendererWasmUnavailable": "WebGPU nicht verfügbar",
|
||||
|
||||
// ── Dokument-Tabs (Zeichnungen/Blätter) ─────────────────────────────────
|
||||
"tabs.drawings": "Zeichnungen",
|
||||
|
||||
// ── Navigator-Panels ─────────────────────────────────────────────────────
|
||||
"nav.drawingLevels": "Zeichnungsebenen",
|
||||
"nav.layers": "Ebenen",
|
||||
|
||||
@@ -190,6 +190,9 @@ export const en: Record<TranslationKey, string> = {
|
||||
"status.rendererWasm": "Engine",
|
||||
"status.rendererWasmUnavailable": "WebGPU unavailable",
|
||||
|
||||
// Document tabs (drawings/sheets).
|
||||
"tabs.drawings": "Drawings",
|
||||
|
||||
// Navigator panels.
|
||||
"nav.drawingLevels": "Drawing levels",
|
||||
"nav.layers": "Layers",
|
||||
|
||||
+34
-50
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user