Ressourcen: Dachtypen-Tab (Schicht-Editor wie Wand/Decke)

Dachaufbauten sind jetzt im Ressourcen-Manager anleg-/editier-/löschbar
(RoofStylesTab auf dem LayeredStylesTab-Rumpf, Ressource roofTypes):
Schichtliste mit Bauteil/Dicke/Fugen-Linienstil, Löschen geschützt solange
ein Dach den Typ referenziert. Host-/App-Handler (patch/add/deleteRoofType)
+ i18n de/en. Vervollständigt die Dach-Schichtlogik (3a986ec) um die UI.
This commit is contained in:
2026-07-10 18:35:34 +02:00
parent b029ce0139
commit 83abc1d87b
6 changed files with 106 additions and 0 deletions
+34
View File
@@ -39,6 +39,7 @@ import type {
OverrideRule, OverrideRule,
Project, Project,
Roof, Roof,
RoofType,
Stair, Stair,
StairType, StairType,
Vec2, Vec2,
@@ -2273,6 +2274,33 @@ export default function App() {
ceilingTypes: (p.ceilingTypes ?? []).filter((ct) => ct.id !== id), ceilingTypes: (p.ceilingTypes ?? []).filter((ct) => ct.id !== id),
}; };
}); });
// ── Dachtypen (Dachaufbauten) — analog Deckentyp ──────────────────────────
const patchRoofType = (id: string, patch: Partial<RoofType>) =>
setProject((p) => ({
...p,
roofTypes: (p.roofTypes ?? []).map((rt) => (rt.id === id ? { ...rt, ...patch } : rt)),
}));
const addRoofType = () =>
setProject((p) => {
const componentId = p.components[0]?.id ?? "";
const roofType: RoofType = {
id: `rt-${Date.now()}`,
name: t("default.roofTypeName"),
layers: [{ componentId, thickness: 0.2 }],
};
return { ...p, roofTypes: [...(p.roofTypes ?? []), roofType] };
});
// Dachtyp löschen — geschützt, solange ihn ein Dach verwendet.
const deleteRoofType = (id: string) =>
setProject((p) => {
const used = (p.roofs ?? []).some((r) => r.roofTypeId === id);
if (used) {
const name = (p.roofTypes ?? []).find((rt) => rt.id === id)?.name ?? id;
notify(t("alert.roofTypeInUse", { name }), "warning");
return p;
}
return { ...p, roofTypes: (p.roofTypes ?? []).filter((rt) => rt.id !== id) };
});
// ── Bauteil-Typen: Tür / Fenster / Treppe (Bibliothek) ──────────────────── // ── Bauteil-Typen: Tür / Fenster / Treppe (Bibliothek) ────────────────────
// Anlegen/Patchen/Löschen analog Wand-/Deckentyp — direkt über setProject // Anlegen/Patchen/Löschen analog Wand-/Deckentyp — direkt über setProject
// (kein eigener Slice). Löschen ist geschützt, solange ein Element den Typ // (kein eigener Slice). Löschen ist geschützt, solange ein Element den Typ
@@ -2954,6 +2982,9 @@ export default function App() {
onPatchCeilingType: patchCeilingType, onPatchCeilingType: patchCeilingType,
onAddCeilingType: addCeilingType, onAddCeilingType: addCeilingType,
onDeleteCeilingType: deleteCeilingType, onDeleteCeilingType: deleteCeilingType,
onPatchRoofType: patchRoofType,
onAddRoofType: addRoofType,
onDeleteRoofType: deleteRoofType,
onImportLineStyles: importLineStyles, onImportLineStyles: importLineStyles,
onImportHatches: importHatches, onImportHatches: importHatches,
selection, selection,
@@ -3941,6 +3972,9 @@ export default function App() {
onPatchCeilingType: patchCeilingType, onPatchCeilingType: patchCeilingType,
onAddCeilingType: addCeilingType, onAddCeilingType: addCeilingType,
onDeleteCeilingType: deleteCeilingType, onDeleteCeilingType: deleteCeilingType,
onPatchRoofType: patchRoofType,
onAddRoofType: addRoofType,
onDeleteRoofType: deleteRoofType,
onAddDoorType: addDoorType, onAddDoorType: addDoorType,
onPatchDoorType: patchDoorType, onPatchDoorType: patchDoorType,
onDeleteDoorType: deleteDoorType, onDeleteDoorType: deleteDoorType,
+8
View File
@@ -638,6 +638,7 @@ export const de = {
"default.ceilingTypeSingle": "Decke {thickness} m", "default.ceilingTypeSingle": "Decke {thickness} m",
"default.wallTypeName": "Neuer Wandtyp", "default.wallTypeName": "Neuer Wandtyp",
"default.ceilingTypeName": "Neuer Deckentyp", "default.ceilingTypeName": "Neuer Deckentyp",
"default.roofTypeName": "Neuer Dachtyp",
"default.doorTypeName": "Neuer Türtyp", "default.doorTypeName": "Neuer Türtyp",
"default.windowTypeName": "Neuer Fenstertyp", "default.windowTypeName": "Neuer Fenstertyp",
"default.stairTypeName": "Neuer Treppentyp", "default.stairTypeName": "Neuer Treppentyp",
@@ -649,6 +650,7 @@ export const de = {
"alert.lineStyleInUse": "Linienstil „{name}\" wird von einer Schraffur verwendet und kann nicht gelöscht werden.", "alert.lineStyleInUse": "Linienstil „{name}\" wird von einer Schraffur verwendet und kann nicht gelöscht werden.",
"alert.wallTypeInUse": "Wandtyp „{name}\" wird von einer Wand verwendet und kann nicht gelöscht werden.", "alert.wallTypeInUse": "Wandtyp „{name}\" wird von einer Wand verwendet und kann nicht gelöscht werden.",
"alert.ceilingTypeInUse": "Deckentyp „{name}\" wird von einer Decke verwendet und kann nicht gelöscht werden.", "alert.ceilingTypeInUse": "Deckentyp „{name}\" wird von einer Decke verwendet und kann nicht gelöscht werden.",
"alert.roofTypeInUse": "Dachtyp „{name}\" wird von einem Dach verwendet und kann nicht gelöscht werden.",
"alert.doorTypeInUse": "Türtyp „{name}\" wird von einer Tür verwendet und kann nicht gelöscht werden.", "alert.doorTypeInUse": "Türtyp „{name}\" wird von einer Tür verwendet und kann nicht gelöscht werden.",
"alert.windowTypeInUse": "Fenstertyp „{name}\" wird von einem Fenster verwendet und kann nicht gelöscht werden.", "alert.windowTypeInUse": "Fenstertyp „{name}\" wird von einem Fenster verwendet und kann nicht gelöscht werden.",
"alert.stairTypeInUse": "Treppentyp „{name}\" wird von einer Treppe verwendet und kann nicht gelöscht werden.", "alert.stairTypeInUse": "Treppentyp „{name}\" wird von einer Treppe verwendet und kann nicht gelöscht werden.",
@@ -662,6 +664,7 @@ export const de = {
"resources.tab.lines": "Linien", "resources.tab.lines": "Linien",
"resources.tab.wallStyles": "Wandstile", "resources.tab.wallStyles": "Wandstile",
"resources.tab.ceilingStyles": "Deckenstile", "resources.tab.ceilingStyles": "Deckenstile",
"resources.tab.roofStyles": "Dachtypen",
"resources.tab.doorStyles": "Türtypen", "resources.tab.doorStyles": "Türtypen",
"resources.tab.windowStyles": "Fenstertypen", "resources.tab.windowStyles": "Fenstertypen",
"resources.tab.stairStyles": "Treppentypen", "resources.tab.stairStyles": "Treppentypen",
@@ -703,6 +706,10 @@ export const de = {
"resources.ceilingStyles.empty": "Noch keine Deckentypen.", "resources.ceilingStyles.empty": "Noch keine Deckentypen.",
"resources.wallStyles.placeholder": "Wandtyp", "resources.wallStyles.placeholder": "Wandtyp",
"resources.ceilingStyles.placeholder": "Deckentyp", "resources.ceilingStyles.placeholder": "Deckentyp",
"resources.roofStyles.hint":
"Je Dachtyp (Schichtaufbau aussen/Eindeckung → innen/Verkleidung) die Schichtfugen als wählbaren Linienstil. Ohne Zuweisung: Haarlinie 0.02 mm.",
"resources.roofStyles.empty": "Noch keine Dachtypen.",
"resources.roofStyles.placeholder": "Dachtyp",
// Bauteil-Typen (Tür/Fenster/Treppe) — Formular-Bibliotheken. // Bauteil-Typen (Tür/Fenster/Treppe) — Formular-Bibliotheken.
"resources.doorTypes.hint": "resources.doorTypes.hint":
@@ -878,6 +885,7 @@ export const de = {
"resources.delete.hatch": "Schraffur „{name}\" löschen", "resources.delete.hatch": "Schraffur „{name}\" löschen",
"resources.delete.wallType": "Wandtyp „{name}\" löschen", "resources.delete.wallType": "Wandtyp „{name}\" löschen",
"resources.delete.ceilingType": "Deckentyp „{name}\" löschen", "resources.delete.ceilingType": "Deckentyp „{name}\" löschen",
"resources.delete.roofType": "Dachtyp „{name}\" löschen",
// Schraffur-/Linien-Typ (Master-Detail-Editor). // Schraffur-/Linien-Typ (Master-Detail-Editor).
"resources.field.type": "Typ", "resources.field.type": "Typ",
+8
View File
@@ -633,6 +633,7 @@ export const en: Record<TranslationKey, string> = {
"default.ceilingTypeSingle": "Ceiling {thickness} m", "default.ceilingTypeSingle": "Ceiling {thickness} m",
"default.wallTypeName": "New wall type", "default.wallTypeName": "New wall type",
"default.ceilingTypeName": "New ceiling type", "default.ceilingTypeName": "New ceiling type",
"default.roofTypeName": "New roof type",
"default.doorTypeName": "New door type", "default.doorTypeName": "New door type",
"default.windowTypeName": "New window type", "default.windowTypeName": "New window type",
"default.stairTypeName": "New stair type", "default.stairTypeName": "New stair type",
@@ -644,6 +645,7 @@ export const en: Record<TranslationKey, string> = {
"alert.lineStyleInUse": "Line style “{name}” is used by a hatch and cannot be deleted.", "alert.lineStyleInUse": "Line style “{name}” is used by a hatch and cannot be deleted.",
"alert.wallTypeInUse": "Wall type “{name}” is used by a wall and cannot be deleted.", "alert.wallTypeInUse": "Wall type “{name}” is used by a wall and cannot be deleted.",
"alert.ceilingTypeInUse": "Ceiling type “{name}” is used by a ceiling and cannot be deleted.", "alert.ceilingTypeInUse": "Ceiling type “{name}” is used by a ceiling and cannot be deleted.",
"alert.roofTypeInUse": "Roof type “{name}” is used by a roof and cannot be deleted.",
"alert.doorTypeInUse": "Door type “{name}” is used by a door and cannot be deleted.", "alert.doorTypeInUse": "Door type “{name}” is used by a door and cannot be deleted.",
"alert.windowTypeInUse": "Window type “{name}” is used by a window and cannot be deleted.", "alert.windowTypeInUse": "Window type “{name}” is used by a window and cannot be deleted.",
"alert.stairTypeInUse": "Stair type “{name}” is used by a stair and cannot be deleted.", "alert.stairTypeInUse": "Stair type “{name}” is used by a stair and cannot be deleted.",
@@ -657,6 +659,7 @@ export const en: Record<TranslationKey, string> = {
"resources.tab.lines": "Lines", "resources.tab.lines": "Lines",
"resources.tab.wallStyles": "Wall styles", "resources.tab.wallStyles": "Wall styles",
"resources.tab.ceilingStyles": "Ceiling styles", "resources.tab.ceilingStyles": "Ceiling styles",
"resources.tab.roofStyles": "Roof types",
"resources.tab.doorStyles": "Door types", "resources.tab.doorStyles": "Door types",
"resources.tab.windowStyles": "Window types", "resources.tab.windowStyles": "Window types",
"resources.tab.stairStyles": "Stair types", "resources.tab.stairStyles": "Stair types",
@@ -698,6 +701,10 @@ export const en: Record<TranslationKey, string> = {
"resources.ceilingStyles.empty": "No ceiling types yet.", "resources.ceilingStyles.empty": "No ceiling types yet.",
"resources.wallStyles.placeholder": "Wall type", "resources.wallStyles.placeholder": "Wall type",
"resources.ceilingStyles.placeholder": "Ceiling type", "resources.ceilingStyles.placeholder": "Ceiling type",
"resources.roofStyles.hint":
"Per roof type (layer build-up outside/covering → inside/lining) the layer joints as a selectable line style. Without assignment: hairline 0.02 mm.",
"resources.roofStyles.empty": "No roof types yet.",
"resources.roofStyles.placeholder": "Roof type",
// Component types (door/window/stair) — form-based libraries. // Component types (door/window/stair) — form-based libraries.
"resources.doorTypes.hint": "resources.doorTypes.hint":
@@ -871,6 +878,7 @@ export const en: Record<TranslationKey, string> = {
"resources.delete.hatch": "Delete hatch “{name}”", "resources.delete.hatch": "Delete hatch “{name}”",
"resources.delete.wallType": "Delete wall type “{name}”", "resources.delete.wallType": "Delete wall type “{name}”",
"resources.delete.ceilingType": "Delete ceiling type “{name}”", "resources.delete.ceilingType": "Delete ceiling type “{name}”",
"resources.delete.roofType": "Delete roof type “{name}”",
// Hatch/line type (master-detail editor). // Hatch/line type (master-detail editor).
"resources.field.type": "Type", "resources.field.type": "Type",
+3
View File
@@ -38,6 +38,9 @@ export function ResourcesPanel() {
onAddWallType: host.onAddWallType, onAddWallType: host.onAddWallType,
onDeleteWallType: host.onDeleteWallType, onDeleteWallType: host.onDeleteWallType,
onPatchCeilingType: host.onPatchCeilingType, onPatchCeilingType: host.onPatchCeilingType,
onPatchRoofType: host.onPatchRoofType,
onAddRoofType: host.onAddRoofType,
onDeleteRoofType: host.onDeleteRoofType,
onAddCeilingType: host.onAddCeilingType, onAddCeilingType: host.onAddCeilingType,
onDeleteCeilingType: host.onDeleteCeilingType, onDeleteCeilingType: host.onDeleteCeilingType,
onImportLineStyles: host.onImportLineStyles, onImportLineStyles: host.onImportLineStyles,
+5
View File
@@ -17,6 +17,7 @@ import type { DisplayMode } from "./types";
import type { import type {
AttributeSource, AttributeSource,
CeilingType, CeilingType,
RoofType,
Component, Component,
ContextObject, ContextObject,
DrawingLevel, DrawingLevel,
@@ -132,6 +133,10 @@ export interface PanelHostValue {
onPatchCeilingType: (id: string, patch: Partial<CeilingType>) => void; onPatchCeilingType: (id: string, patch: Partial<CeilingType>) => void;
onAddCeilingType: () => void; onAddCeilingType: () => void;
onDeleteCeilingType: (id: string) => void; onDeleteCeilingType: (id: string) => void;
/** Dachtypen (Dachaufbauten): immutable Änderung/Anlage/Löschung. */
onPatchRoofType: (id: string, patch: Partial<RoofType>) => void;
onAddRoofType: () => void;
onDeleteRoofType: (id: string) => void;
/** Import fertiger (id-loser) Linienstile/Schraffuren (.lin/.pat). */ /** Import fertiger (id-loser) Linienstile/Schraffuren (.lin/.pat). */
onImportLineStyles: (styles: Omit<LineStyle, "id">[]) => void; onImportLineStyles: (styles: Omit<LineStyle, "id">[]) => void;
onImportHatches: (hatches: Omit<HatchStyle, "id">[]) => void; onImportHatches: (hatches: Omit<HatchStyle, "id">[]) => void;
+48
View File
@@ -20,6 +20,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
import type { PointerEvent as ReactPointerEvent } from "react"; import type { PointerEvent as ReactPointerEvent } from "react";
import type { import type {
CeilingType, CeilingType,
RoofType,
Component, Component,
ComponentMaterial, ComponentMaterial,
DoorType, DoorType,
@@ -1893,6 +1894,39 @@ function CeilingStylesTab({
); );
} }
/**
* Tab „Dachtypen" — das geneigte Gegenstück zu {@link CeilingStylesTab}: gleicher
* {@link LayeredStylesTab}-Rumpf (Schichtaufbau aussen/Eindeckung → innen/
* Verkleidung), Ressource `roofTypes`. Vorschau in Decken-Orientierung
* (gestapelt) — für die Schichtreihenfolge/-dicken ausreichend.
*/
function RoofStylesTab({
project,
onPatchRoofType,
onAddRoofType,
onDeleteRoofType,
}: {
project: Project;
onPatchRoofType: (id: string, patch: Partial<RoofType>) => void;
onAddRoofType: () => void;
onDeleteRoofType: (id: string) => void;
}) {
return (
<LayeredStylesTab
project={project}
types={project.roofTypes ?? []}
orientation="ceiling"
hintKey="resources.roofStyles.hint"
emptyKey="resources.roofStyles.empty"
placeholderKey="resources.roofStyles.placeholder"
deleteKeyPrefix="resources.delete.roofType"
onPatch={onPatchRoofType}
onAdd={onAddRoofType}
onDelete={onDeleteRoofType}
/>
);
}
/** /**
* Datei-Import-Schaltfläche (verstecktes <input type=file>). Liest die gewählte * Datei-Import-Schaltfläche (verstecktes <input type=file>). Liest die gewählte
* Datei als Text und reicht ihn nach oben. Für .lin/.pat-Import. * Datei als Text und reicht ihn nach oben. Für .lin/.pat-Import.
@@ -2957,6 +2991,7 @@ export type TabId =
| "lines" | "lines"
| "wallStyles" | "wallStyles"
| "ceilingStyles" | "ceilingStyles"
| "roofStyles"
| "doorStyles" | "doorStyles"
| "windowStyles" | "windowStyles"
| "stairStyles" | "stairStyles"
@@ -2969,6 +3004,7 @@ const TABS: { id: TabId; labelKey: string }[] = [
{ id: "lines", labelKey: "resources.tab.lines" }, { id: "lines", labelKey: "resources.tab.lines" },
{ id: "wallStyles", labelKey: "resources.tab.wallStyles" }, { id: "wallStyles", labelKey: "resources.tab.wallStyles" },
{ id: "ceilingStyles", labelKey: "resources.tab.ceilingStyles" }, { id: "ceilingStyles", labelKey: "resources.tab.ceilingStyles" },
{ id: "roofStyles", labelKey: "resources.tab.roofStyles" },
{ id: "doorStyles", labelKey: "resources.tab.doorStyles" }, { id: "doorStyles", labelKey: "resources.tab.doorStyles" },
{ id: "windowStyles", labelKey: "resources.tab.windowStyles" }, { id: "windowStyles", labelKey: "resources.tab.windowStyles" },
{ id: "stairStyles", labelKey: "resources.tab.stairStyles" }, { id: "stairStyles", labelKey: "resources.tab.stairStyles" },
@@ -2995,6 +3031,10 @@ export interface ResourceManagerHandlers {
onPatchCeilingType: (id: string, patch: Partial<CeilingType>) => void; onPatchCeilingType: (id: string, patch: Partial<CeilingType>) => void;
onAddCeilingType: () => void; onAddCeilingType: () => void;
onDeleteCeilingType: (id: string) => void; onDeleteCeilingType: (id: string) => void;
/** Dachtypen (Dachaufbauten): immutable Änderung/Anlage/Löschung. */
onPatchRoofType: (id: string, patch: Partial<RoofType>) => void;
onAddRoofType: () => void;
onDeleteRoofType: (id: string) => void;
/** Import: fügt fertige (id-lose) Linienstile/Schraffuren hinzu (.lin/.pat). */ /** Import: fügt fertige (id-lose) Linienstile/Schraffuren hinzu (.lin/.pat). */
onImportLineStyles: (styles: Omit<LineStyle, "id">[]) => void; onImportLineStyles: (styles: Omit<LineStyle, "id">[]) => void;
onImportHatches: (hatches: Omit<HatchStyle, "id">[]) => void; onImportHatches: (hatches: Omit<HatchStyle, "id">[]) => void;
@@ -3219,6 +3259,14 @@ export function ResourceManager({
onDeleteCeilingType={handlers.onDeleteCeilingType} onDeleteCeilingType={handlers.onDeleteCeilingType}
/> />
)} )}
{tab === "roofStyles" && (
<RoofStylesTab
project={project}
onPatchRoofType={handlers.onPatchRoofType}
onAddRoofType={handlers.onAddRoofType}
onDeleteRoofType={handlers.onDeleteRoofType}
/>
)}
{tab === "doorStyles" && ( {tab === "doorStyles" && (
<DoorStylesTab <DoorStylesTab
project={project} project={project}