diff --git a/src/ui/ResourceManager.tsx b/src/ui/ResourceManager.tsx index 9d5d639..57a39fc 100644 --- a/src/ui/ResourceManager.tsx +++ b/src/ui/ResourceManager.tsx @@ -257,98 +257,8 @@ function FieldRow({ ); } -// ── Tabellen-Gerüst ──────────────────────────────────────────────────────── - -/** Eine Spaltendefinition: Titel-Key + optionale rechtsbündige Ausrichtung. */ -interface ResColumn { - /** i18n-Key des Spaltentitels (leerer String = titellose Spalte). */ - titleKey: string; - /** Zahlen-Spalten rechtsbündig ausrichten (Titel + Zellen). */ - align?: "right"; - /** Optionaler i18n-Key für den Tooltip auf dem Spaltentitel. */ - hintKey?: string; -} - -/** - * Tabelle als CSS-Grid: eine sticky Kopfzeile mit Spaltentiteln, darunter eine - * Datenzeile je Item. Kopf und Zeilen erben `gridTemplateColumns` vom äußeren - * Grid, damit die Spalten exakt fluchten. Datenzeilen werden als Kinder - * übergeben (jede Zeile = `ResRow`). - */ -function ResTable({ - columns, - template, - children, -}: { - columns: ResColumn[]; - template: string; - children: React.ReactNode; -}) { - return ( -
-
- {columns.map((c, i) => ( -
- {c.titleKey ? t(c.titleKey) : ""} -
- ))} -
- {children} -
- ); -} - -/** Eine Datenzeile der Tabelle (Zellen als Kinder, eine je Spalte). */ -function ResRow({ children }: { children: React.ReactNode }) { - return
{children}
; -} - -/** Eine Tabellenzelle; optional rechtsbündig (für Zahlen). */ -function ResCell({ - children, - align, - emphasis = false, -}: { - children: React.ReactNode; - align?: "right"; - emphasis?: boolean; -}) { - return ( -
- {children} -
- ); -} - // ── Bauteile (Components) ────────────────────────────────────────────────── -// Spalten: Name | Vordergrund | Hintergrund | Schnittschraffur | Ansichtsschraffur | Prio | Textur | Material | (löschen). -const COMPONENT_COLUMNS: ResColumn[] = [ - { titleKey: "resources.col.name" }, - { titleKey: "resources.col.foreground", hintKey: "resources.col.foreground.hint" }, - { titleKey: "resources.col.background", hintKey: "resources.col.background.hint" }, - { titleKey: "resources.col.hatch" }, - { titleKey: "resources.col.viewHatch" }, - { - titleKey: "resources.col.prio", - align: "right", - hintKey: "resources.col.prio.hint", - }, - { titleKey: "resources.col.texture" }, - { titleKey: "resources.col.material" }, - { titleKey: "" }, -]; -const COMPONENT_TEMPLATE = - "minmax(120px, 1fr) auto auto minmax(120px, 0.8fr) minmax(120px, 0.8fr) 64px minmax(90px, 0.7fr) minmax(130px, 0.9fr) 36px"; - /** * Material-Zelle der Bauteil-Tabelle: ein Button, der den aktuellen Material- * Namen zeigt (oder „ohne") und ein Auswahl-Modal öffnet. Die Zuweisung läuft @@ -803,7 +713,24 @@ function ImagePickButton({ onFile }: { onFile: (file: File | null) => void }) { ); } -function ComponentRow({ +/** Kleiner Farb-Swatch als Listen-Thumbnail für Bauteile ohne Schraffur. */ +function ColorSwatch({ color, size = 30 }: { color: string; size?: number }) { + return ( + + ); +} + +/** Detail-Panel EINES Bauteils (rechte Seite des Master-Detail-Layouts). */ +function ComponentDetail({ component, hatches, onPatch, @@ -820,74 +747,91 @@ function ComponentRow({ { value: "", label: t("resources.viewHatch.none") }, ...hatchOptions, ]; + const previewHatch = hatches.find((h) => h.id === component.hatchId); return ( - - - +
+ onPatch({ name })} placeholder={t("resources.components.placeholder")} + onChange={(e) => onPatch({ name: e.target.value })} /> - - - {/* Vordergrund = Farbe der Muster-/Schraffurlinien. Fehlt sie, greift - als Anzeige-Fallback `color`; das Setzen definiert `foreground`. */} - onPatch({ foreground })} - /> - - - {/* Hintergrund = Füllung (Poché). Fehlt sie, gilt `color` als Fallback. */} - onPatch({ background })} - /> - - - onPatch({ hatchId })} - options={hatchOptions} - /> - - - onPatch({ viewHatchId: viewHatchId || undefined })} - options={viewHatchOptions} - /> - - - onPatch({ joinPriority })} - step={10} - min={0} - /> - - - onPatch({ texture3d: v || undefined })} - placeholder={t("resources.texture.empty")} - mono - /> - - - onPatch({ material })} - onClear={() => onPatch({ material: undefined })} - /> - - - - +
+ +
+ {previewHatch ? ( + + ) : ( + + )} +
+ +
+ + {/* Vordergrund = Farbe der Muster-/Schraffurlinien. Fehlt sie, greift + als Anzeige-Fallback `color`; das Setzen definiert `foreground`. */} + onPatch({ foreground })} + /> + + + {/* Hintergrund = Füllung (Poché). Fehlt sie, gilt `color` als Fallback. */} + onPatch({ background })} + /> + + + onPatch({ hatchId })} + options={hatchOptions} + /> + + + onPatch({ viewHatchId: viewHatchId || undefined })} + options={viewHatchOptions} + /> + + + onPatch({ joinPriority })} + step={10} + min={0} + /> + + + onPatch({ texture3d: v || undefined })} + placeholder={t("resources.texture.empty")} + mono + /> + + + onPatch({ material })} + onClear={() => onPatch({ material: undefined })} + /> + +
+ ); } @@ -902,24 +846,58 @@ function ComponentsTab({ onAddComponent: () => void; onDeleteComponent: (id: string) => void; }) { + const [selectedId, setSelectedId] = useState(null); + const components = project.components; + // Auswahl gegen die Liste validieren (nach Delete/Add stabil bleiben). + const selected = components.find((c) => c.id === selectedId) ?? components[0]; + return (
{t("resources.components.hint")}
- - {project.components.map((c) => ( - onPatchComponent(c.id, patch)} - onDelete={() => onDeleteComponent(c.id)} - /> - ))} - {project.components.length === 0 && ( - - )} - - +
+
+ {components.map((c) => { + const hatch = project.hatches.find((h) => h.id === c.hatchId); + return ( + + ); + })} + {components.length === 0 && ( +
{t("resources.components.empty")}
+ )} +
+ +
+
+
+ {selected ? ( + onPatchComponent(selected.id, patch)} + onDelete={() => onDeleteComponent(selected.id)} + /> + ) : ( +
{t("resources.components.empty")}
+ )} +
+
); } @@ -2050,16 +2028,6 @@ function MaterialsTab() { // ── Gemeinsame kleine Bausteine ──────────────────────────────────────────── -function AddButton({ label, onClick }: { label: string; onClick: () => void }) { - return ( -
- -
- ); -} - function EmptyState({ text }: { text: string }) { return
{text}
; }