Öffnung: ⚙-Knopf springt in den Tür-/Fenstertyp-Editor

Discoverability für die vertieften Bauteil-Typen: neben dem Typ-Dropdown
einer gewählten Öffnung öffnet ein ⚙-Knopf das Ressourcen-Fenster direkt
beim passenden Typeditor-Tab (Tür→doorStyles, Fenster→windowStyles).
- ResourceManager: initialTab-Prop (springt auch bei bereits offenem
  Fenster auf den angeforderten Tab).
- host.onEditOpeningType(kind) + App-Wiring (resourcesTab-State).
tsc + vitest 620 grün.
This commit is contained in:
2026-07-09 01:44:13 +02:00
parent 4ac2ecb504
commit 586c1c99bf
7 changed files with 56 additions and 5 deletions
+9 -1
View File
@@ -93,7 +93,7 @@ import { Viewport3D } from "./viewport/Viewport3D";
import type { DisplayResolver, ViewportContextInfo } from "./viewport/Viewport3D"; import type { DisplayResolver, ViewportContextInfo } from "./viewport/Viewport3D";
import type { Pick3dHit } from "./viewport/Wasm3DViewport"; 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, TabId as ResourceTabId } from "./ui/ResourceManager";
import { LayoutSheetView } from "./ui/LayoutSheet"; import { LayoutSheetView } from "./ui/LayoutSheet";
import { import {
createLayout, createLayout,
@@ -408,6 +408,9 @@ export default function App() {
// bleibt lokal. Wird über die Oberleiste geöffnet und über X / Hintergrund- // bleibt lokal. Wird über die Oberleiste geöffnet und über X / Hintergrund-
// Klick / Esc geschlossen. // Klick / Esc geschlossen.
const [resourcesOpen, setResourcesOpen] = useState(false); const [resourcesOpen, setResourcesOpen] = useState(false);
// Angeforderter Start-Tab des Ressourcen-Fensters (z. B. „doorStyles", wenn man
// von einer gewählten Tür in den Typeditor springt).
const [resourcesTab, setResourcesTab] = useState<ResourceTabId>("components");
// Aktives Layout-Blatt im HAUPT-Viewport (DOSSIER A3, Phase 2b): Id des offenen // Aktives Layout-Blatt im HAUPT-Viewport (DOSSIER A3, Phase 2b): Id des offenen
// Layouts oder null (= normale Modell-Ansicht). Transienter UI-State; das // Layouts oder null (= normale Modell-Ansicht). Transienter UI-State; das
@@ -3109,6 +3112,10 @@ export default function App() {
if (selection?.kind === "opening") if (selection?.kind === "opening")
updateOpening(selection.id, { typeId: typeId || undefined }); updateOpening(selection.id, { typeId: typeId || undefined });
}, },
onEditOpeningType: (kind) => {
setResourcesTab(kind === "door" ? "doorStyles" : "windowStyles");
setResourcesOpen(true);
},
// ── Treppen-Attribute (nur bei selektierter Treppe) ──────────────────── // ── Treppen-Attribute (nur bei selektierter Treppe) ────────────────────
onSetStairShape: (shape) => { onSetStairShape: (shape) => {
if (selection?.kind === "stair") updateStair(selection.id, { shape }); if (selection?.kind === "stair") updateStair(selection.id, { shape });
@@ -4910,6 +4917,7 @@ export default function App() {
<ResourceManager <ResourceManager
project={project} project={project}
handlers={resourceHandlers} handlers={resourceHandlers}
initialTab={resourcesTab}
onClose={() => setResourcesOpen(false)} onClose={() => setResourcesOpen(false)}
/> />
)} )}
+1
View File
@@ -340,6 +340,7 @@ export const de = {
// ── Öffnungs-Attribute (Object-Info-Panel) ────────────────────────────── // ── Öffnungs-Attribute (Object-Info-Panel) ──────────────────────────────
"objinfo.opening.section": "Öffnung", "objinfo.opening.section": "Öffnung",
"objinfo.opening.type": "Typ", "objinfo.opening.type": "Typ",
"objinfo.opening.editType": "Typ bearbeiten (Ressourcen)",
"objinfo.stair.type": "Typ", "objinfo.stair.type": "Typ",
"objinfo.type.none": "(kein Typ)", "objinfo.type.none": "(kein Typ)",
"objinfo.opening.kind": "Art", "objinfo.opening.kind": "Art",
+1
View File
@@ -340,6 +340,7 @@ export const en: Record<TranslationKey, string> = {
// ── Opening attributes (object info panel) ────────────────────────────── // ── Opening attributes (object info panel) ──────────────────────────────
"objinfo.opening.section": "Opening", "objinfo.opening.section": "Opening",
"objinfo.opening.type": "Type", "objinfo.opening.type": "Type",
"objinfo.opening.editType": "Edit type (Resources)",
"objinfo.stair.type": "Type", "objinfo.stair.type": "Type",
"objinfo.type.none": "(no type)", "objinfo.type.none": "(no type)",
"objinfo.opening.kind": "Type", "objinfo.opening.kind": "Type",
+12 -2
View File
@@ -578,7 +578,8 @@ export function OpeningSection({
<div className="objinfo-sep" /> <div className="objinfo-sep" />
<div className="objinfo-section-label">{t("objinfo.opening.section")}</div> <div className="objinfo-section-label">{t("objinfo.opening.section")}</div>
{/* Tür-/Fenstertyp (Bibliothek) — je nach Art aus doorTypes bzw. windowTypes. */} {/* Tür-/Fenstertyp (Bibliothek) — je nach Art aus doorTypes bzw. windowTypes.
Der ⚙-Knopf springt direkt in den Typeditor (Ressourcen-Fenster). */}
<div className="objinfo-field"> <div className="objinfo-field">
<span className="objinfo-flabel">{t("objinfo.opening.type")}</span> <span className="objinfo-flabel">{t("objinfo.opening.type")}</span>
<Dropdown <Dropdown
@@ -592,8 +593,17 @@ export function OpeningSection({
).map((ty) => ({ value: ty.id, label: ty.name })), ).map((ty) => ({ value: ty.id, label: ty.name })),
]} ]}
title={t("objinfo.opening.type")} title={t("objinfo.opening.type")}
width={130} width={106}
/> />
<button
type="button"
className="objinfo-typebtn"
title={t("objinfo.opening.editType")}
aria-label={t("objinfo.opening.editType")}
onClick={() => host.onEditOpeningType(isDoor ? "door" : "window")}
>
</button>
</div> </div>
{/* Art-Segment Fenster/Tür. */} {/* Art-Segment Fenster/Tür. */}
+5
View File
@@ -294,6 +294,11 @@ export interface PanelHostValue {
onSetOpeningLintelLines: (lintelLines: "keine" | "innen" | "aussen" | "beide") => void; onSetOpeningLintelLines: (lintelLines: "keine" | "innen" | "aussen" | "beide") => void;
/** Weist der Öffnung einen Tür-/Fenstertyp (Bibliothek) zu; "" löst die Zuweisung. */ /** Weist der Öffnung einen Tür-/Fenstertyp (Bibliothek) zu; "" löst die Zuweisung. */
onSetOpeningType: (typeId: string) => void; onSetOpeningType: (typeId: string) => void;
/**
* Öffnet das Ressourcen-Fenster direkt beim Tür- bzw. Fenstertyp-Editor
* (Discoverability: von der gewählten Öffnung in den Typeditor springen).
*/
onEditOpeningType: (kind: "door" | "window") => void;
// ── Treppen-Attribute (Object-Info-Panel; nur die selektierte Treppe) ─── // ── Treppen-Attribute (Object-Info-Panel; nur die selektierte Treppe) ───
/** Setzt die Grundform (gerade/L/Wendel). */ /** Setzt die Grundform (gerade/L/Wendel). */
+18
View File
@@ -4044,6 +4044,24 @@ body {
letter-spacing: 0.04em; letter-spacing: 0.04em;
margin: 2px 2px 4px; margin: 2px 2px 4px;
} }
/* „Typ bearbeiten"-Knopf (⚙) neben dem Öffnungstyp-Dropdown. */
.objinfo-typebtn {
margin-left: 4px;
width: 22px;
height: 22px;
padding: 0;
border: 1px solid var(--border, #444);
border-radius: 4px;
background: transparent;
color: var(--fg, inherit);
cursor: pointer;
font-size: 12px;
line-height: 1;
}
.objinfo-typebtn:hover {
background: var(--hover, rgba(127, 127, 127, 0.18));
}
/* Mess-Werkzeug: Live-Messwerte im Objekt-Info-Panel. */ /* Mess-Werkzeug: Live-Messwerte im Objekt-Info-Panel. */
.objinfo-measure { .objinfo-measure {
margin: 2px 2px 8px; margin: 2px 2px 8px;
+10 -2
View File
@@ -2951,7 +2951,7 @@ function EmptyState({ text }: { text: string }) {
// ── Manager-Schublade ────────────────────────────────────────────────────── // ── Manager-Schublade ──────────────────────────────────────────────────────
type TabId = export type TabId =
| "components" | "components"
| "hatches" | "hatches"
| "lines" | "lines"
@@ -3064,12 +3064,20 @@ export function ResourceManager({
project, project,
onClose, onClose,
handlers, handlers,
initialTab,
}: { }: {
project: Project; project: Project;
onClose: () => void; onClose: () => void;
handlers: ResourceManagerHandlers; handlers: ResourceManagerHandlers;
/** Beim Öffnen (bzw. bei erneutem Sprung) direkt aktiver Tab, z. B. „doorStyles". */
initialTab?: TabId;
}) { }) {
const [tab, setTab] = useState<TabId>("components"); const [tab, setTab] = useState<TabId>(initialTab ?? "components");
// Erneuter Sprung aus einem Element heraus (z. B. „Typ bearbeiten" an einer
// Öffnung): auf den angeforderten Tab wechseln, auch wenn das Fenster schon offen ist.
useEffect(() => {
if (initialTab) setTab(initialTab);
}, [initialTab]);
const [rect, setRect] = useState<WindowRect>(initialRect); const [rect, setRect] = useState<WindowRect>(initialRect);
// Verschieben über die Kopfzeile: Cursor linke/obere Kante festhalten und die // Verschieben über die Kopfzeile: Cursor linke/obere Kante festhalten und die