diff --git a/src/i18n/de.ts b/src/i18n/de.ts index a22f5f7..9881d85 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -545,6 +545,9 @@ export const de = { "resources.components.placeholder": "Bauteil", "resources.col.name": "Name", "resources.col.color": "Farbe", + "resources.col.abbrev": "Kürzel", + "resources.col.abbrev.hint": "Kurz-Code für Wandtyp-Dropdowns (z. B. «BET», «HLZ»)", + "resources.col.abbrev.placeholder": "z. B. BET", "resources.col.hatch": "Schnittschraffur", "resources.col.viewHatch": "Ansichtsschraffur", "resources.viewHatch.none": "(keine)", diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 9df258d..6b40492 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -539,6 +539,9 @@ export const en: Record = { "resources.components.placeholder": "Component", "resources.col.name": "Name", "resources.col.color": "Color", + "resources.col.abbrev": "Code", + "resources.col.abbrev.hint": "Short code for wall-type dropdowns (e.g. «CON», «BRK»)", + "resources.col.abbrev.placeholder": "e.g. CON", "resources.col.hatch": "Section hatch", "resources.col.viewHatch": "View hatch", "resources.viewHatch.none": "(none)", diff --git a/src/model/types.ts b/src/model/types.ts index 68cf01c..90a2de1 100644 --- a/src/model/types.ts +++ b/src/model/types.ts @@ -227,6 +227,12 @@ export interface Component { * Render-Modus „textured". */ material?: ComponentMaterial; + /** + * Optionales Kürzel für Wandtyp-Labels (z. B. „BET", „HLZ", „GKB", „DAE"). + * Wird von `wallTypeLabel()` genutzt, um Wandtyp-Dropdowns kompakt darzustellen + * (z. B. „BET 24" statt dem vollen Namen). Fehlt es, greift der Name. + */ + abbrev?: string; /** Verschneidungs-Rang: höher läuft am Stoß durch (Backbone). */ joinPriority: number; } @@ -1374,6 +1380,35 @@ export const collectVisibleCodes = (layers: LayerCategory[]): Set => { export const wallTypeThickness = (wt: WallType): number => wt.layers.reduce((sum, l) => sum + l.thickness, 0); +/** + * Kompakter Anzeigetext für einen Wandtyp im Dropdown (VW-Stil). + * - Einschichtig mit Kürzel: „BET 24" (Kürzel + Dicke in cm) + * - Mehrschichtig mit Kürzeln: „GKB·BET·DAE" + Gesamtdicke „(25.5)" + * - Ohne Kürzel: Name bleibt — kein Rückfall auf rohen Namen, nur keine Kurzform. + * + * `project.components` wird genutzt, um `Component.abbrev` aufzulösen. + * Ist kein abbrev gesetzt, erscheint der volle Name unverändert. + */ +export function wallTypeLabel( + wt: WallType | CeilingType, + components: Component[], +): string { + const resolve = (id: string) => components.find((c) => c.id === id); + const totalCm = +(wallTypeThickness(wt) * 100).toFixed(1); + // cm-Zahl: "24" statt "24.0", "17.5" bleibt "17.5" + const cmStr = totalCm % 1 === 0 ? String(totalCm | 0) : String(totalCm); + + // Alle Schichten haben ein Kürzel → Kurzform bauen + const abbrevs = wt.layers.map((l) => resolve(l.componentId)?.abbrev ?? ""); + const allHaveAbbrev = abbrevs.every((a) => a.length > 0); + if (allHaveAbbrev) { + if (wt.layers.length === 1) return `${abbrevs[0]} ${cmStr}`; + return `${abbrevs.join("·")} (${cmStr})`; + } + // Fallback: voller Name + return wt.name; +} + /** Formatiert Meter mit zwei Nachkommastellen, z. B. "0.35 m". */ export const formatM = (meters: number): string => meters.toFixed(2) + " m"; diff --git a/src/panels/AttributesPanel.tsx b/src/panels/AttributesPanel.tsx index 55b0549..83c541e 100644 --- a/src/panels/AttributesPanel.tsx +++ b/src/panels/AttributesPanel.tsx @@ -24,7 +24,7 @@ import type { ReactNode } from "react"; import { t } from "../i18n"; -import { formatM } from "../model/types"; +import { formatM, wallTypeLabel } from "../model/types"; import { PEN_WEIGHTS } from "../model/types"; import type { AttributeSource } from "../model/types"; import { usePanelHost } from "./host"; @@ -75,7 +75,11 @@ export function AttributesPanel() { value={host.activeWallTypeId} disabled={!host.toolsEnabled} onChange={(v) => host.onActiveWallTypeId(v)} - options={project.wallTypes.map((wt) => ({ value: wt.id, label: wt.name }))} + options={project.wallTypes.map((wt) => ({ + value: wt.id, + label: wallTypeLabel(wt, project.components), + title: wt.name, + }))} /> diff --git a/src/panels/ObjectInfoPanel.tsx b/src/panels/ObjectInfoPanel.tsx index 0314d6f..7fc956e 100644 --- a/src/panels/ObjectInfoPanel.tsx +++ b/src/panels/ObjectInfoPanel.tsx @@ -581,10 +581,8 @@ export function CeilingSection({ onChange={(id) => host.onSetCeilingType(id)} options={ceiling.ceilingTypes.map((ct) => ({ value: ct.id, - label: t("objinfo.wall.presetLabel", { - name: ct.name, - thickness: ct.thickness.toFixed(2), - }), + label: ct.label, + title: ct.name, }))} title={t("objinfo.ceiling.preset")} width={150} @@ -717,10 +715,8 @@ export function WallSection({ onChange={(id) => host.onSetWallType(id)} options={wall.wallTypes.map((wt) => ({ value: wt.id, - label: t("objinfo.wall.presetLabel", { - name: wt.name, - thickness: wt.thickness.toFixed(2), - }), + label: wt.label, + title: wt.name, }))} title={t("objinfo.wall.preset")} width={150} diff --git a/src/state/selectionInfo.ts b/src/state/selectionInfo.ts index 52ff0c7..abcd199 100644 --- a/src/state/selectionInfo.ts +++ b/src/state/selectionInfo.ts @@ -15,6 +15,7 @@ import { flattenCategories, getCeilingType, getWallType, + wallTypeLabel, wallTypeThickness, } from "../model/types"; import type { @@ -48,6 +49,8 @@ import { stairGeometry, stairBBox } from "../geometry/stair"; export interface WallTypeChoice { id: string; name: string; + /** Kurz-Label für das Dropdown (aus wallTypeLabel: Kürzel+Dicke oder Name). */ + label: string; /** Gesamtdicke (Meter) — informativ im Dropdown-Label. */ thickness: number; /** Anzahl Schichten (1 = einschichtig). */ @@ -359,6 +362,7 @@ function wallSelection(project: Project, wall: Wall): Selection { wallTypes: project.wallTypes.map((t) => ({ id: t.id, name: t.name, + label: wallTypeLabel(t, project.components), thickness: wallTypeThickness(t), layerCount: t.layers.length, })), @@ -414,6 +418,7 @@ function ceilingSelection(project: Project, ceiling: Ceiling): Selection { ceilingTypes: (project.ceilingTypes ?? []).map((t) => ({ id: t.id, name: t.name, + label: wallTypeLabel(t, project.components), thickness: wallTypeThickness(t), layerCount: t.layers.length, })), diff --git a/src/styles.css b/src/styles.css index df17662..ada78b2 100644 --- a/src/styles.css +++ b/src/styles.css @@ -4040,7 +4040,8 @@ body { padding-left: 56px; } .objinfo-subfield .tb-dd-trigger { - width: 100%; + width: auto; + max-width: 140px; } /* ── Attribute-Palette (AttributesPanel) ─────────────────────────────── */ diff --git a/src/ui/ResourceManager.tsx b/src/ui/ResourceManager.tsx index d9ddbe8..d40db21 100644 --- a/src/ui/ResourceManager.tsx +++ b/src/ui/ResourceManager.tsx @@ -763,6 +763,13 @@ function ComponentDetail({
+ + onPatch({ abbrev: v || undefined })} + placeholder={t("resources.col.abbrev.placeholder")} + /> +