diff --git a/src/i18n/de.ts b/src/i18n/de.ts index c22a0cb..bd551dd 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -500,6 +500,10 @@ export const de = { "resources.col.prio.hint": "Höhere Priorität läuft am Stoß durch", "resources.col.texture": "Textur", "resources.col.material": "Material", + "resources.col.foreground": "Vordergrund", + "resources.col.foreground.hint": "Farbe der Muster-/Schraffurlinien (Vordergrund)", + "resources.col.background": "Hintergrund", + "resources.col.background.hint": "Füllfarbe (Poché/Hintergrund)", "resources.delete.component": "Bauteil „{name}\" löschen", // 3D-Material (PBR) — Bibliotheks-Auswahl + Upload. @@ -553,6 +557,22 @@ export const de = { "resources.lineStyle.none": "— ohne —", "resources.delete.hatch": "Schraffur „{name}\" löschen", + // Schraffur-/Linien-Typ (Master-Detail-Editor). + "resources.field.type": "Typ", + "resources.field.lines": "Striche", + "resources.field.image": "Bild", + "resources.field.scaleX": "Skalierung X", + "resources.field.scaleY": "Skalierung Y", + "resources.field.rotation": "Rotation °", + "resources.field.amplitude": "Amplitude", + "resources.field.wavelength": "Wellenlänge", + "resources.hatchKind.vector": "Vektor", + "resources.hatchKind.image": "Bild", + "resources.lines.parallel": "Parallel", + "resources.lines.random": "Zufällig", + "resources.lineKind.dash": "Strich", + "resources.lineKind.zigzag": "Zickzack", + // Schraffur-Muster. "pattern.none": "Ohne", "pattern.solid": "Vollfläche", diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 5f7bd8c..5a0159c 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -496,6 +496,10 @@ export const en: Record = { "resources.col.prio.hint": "Higher priority runs through at the join", "resources.col.texture": "Texture", "resources.col.material": "Material", + "resources.col.foreground": "Foreground", + "resources.col.foreground.hint": "Color of the pattern/hatch lines (foreground)", + "resources.col.background": "Background", + "resources.col.background.hint": "Fill color (poché/background)", "resources.delete.component": "Delete component “{name}”", // 3D material (PBR) — built-in library + upload. @@ -548,6 +552,22 @@ export const en: Record = { "resources.lineStyle.none": "— none —", "resources.delete.hatch": "Delete hatch “{name}”", + // Hatch/line type (master-detail editor). + "resources.field.type": "Type", + "resources.field.lines": "Lines", + "resources.field.image": "Image", + "resources.field.scaleX": "Scale X", + "resources.field.scaleY": "Scale Y", + "resources.field.rotation": "Rotation °", + "resources.field.amplitude": "Amplitude", + "resources.field.wavelength": "Wavelength", + "resources.hatchKind.vector": "Vector", + "resources.hatchKind.image": "Image", + "resources.lines.parallel": "Parallel", + "resources.lines.random": "Random", + "resources.lineKind.dash": "Dash", + "resources.lineKind.zigzag": "Zigzag", + "pattern.none": "None", "pattern.solid": "Solid", "pattern.insulation": "Insulation", diff --git a/src/styles.css b/src/styles.css index 400e16c..1a03053 100644 --- a/src/styles.css +++ b/src/styles.css @@ -2315,6 +2315,178 @@ body { background: var(--panel-2); } +/* ── Master-Detail (Schraffuren / Linien): Liste links, Detail rechts ──────── */ +.res-md { + display: grid; + grid-template-columns: 240px 1fr; + gap: 14px; + align-items: start; + min-height: 320px; +} +.res-md-list { + display: flex; + flex-direction: column; + gap: 2px; + padding: 6px; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--panel-2); + max-height: 62vh; + overflow-y: auto; +} +.res-md-row { + display: flex; + align-items: center; + gap: 9px; + width: 100%; + padding: 6px 8px; + border: 1px solid transparent; + border-radius: 8px; + background: transparent; + color: var(--ink); + font-family: inherit; + font-size: 12px; + text-align: left; + cursor: pointer; + transition: background 0.12s, border-color 0.12s; +} +.res-md-row:hover { + background: var(--accent-dim); +} +.res-md-row.active { + background: var(--accent-dim); + border-color: var(--accent-border); + color: var(--accent); +} +.res-md-row-thumb { + flex: 0 0 auto; + display: inline-flex; +} +.res-md-row-name { + flex: 1 1 auto; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 550; +} +/* Linien-Zeile: Name oben schmal, Swatch daneben rechts. */ +.res-md-row-line .res-line-preview { + flex: 0 0 auto; + width: 150px; + min-width: 0; +} +.res-md-listfoot { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding: 8px 4px 2px; +} +.res-md-empty { + padding: 22px 14px; + text-align: center; + font-size: 12px; + color: var(--muted); +} + +/* Detail-Panel (rechts). */ +.res-md-detail { + border: 1px solid var(--border); + border-radius: 10px; + background: var(--panel-2); + min-height: 300px; +} +.res-md-detail-inner { + display: flex; + flex-direction: column; + gap: 14px; + padding: 14px 16px 18px; +} +.res-md-head { + display: flex; + align-items: center; + gap: 10px; +} +.res-md-rename { + flex: 1 1 auto; + font-size: 13px; + font-weight: 600; +} +.res-md-preview { + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--input); + color: var(--ink); +} +.res-md-preview .res-line-preview { + border: none; + background: transparent; +} + +/* Detail-Felder: Label links, Steuerelement rechts. */ +.res-fields { + display: flex; + flex-direction: column; + gap: 8px; +} +.res-field { + display: grid; + grid-template-columns: 120px 1fr; + align-items: center; + gap: 12px; +} +.res-field-label { + font-size: 12px; + color: var(--muted); +} +.res-field-control { + display: flex; + align-items: center; + min-width: 0; +} +.res-field-control > * { + min-width: 0; +} + +/* Segmentierter Umschalter (Pillen-Gruppe). */ +.res-seg { + display: inline-flex; + gap: 2px; + padding: 2px; + border: 1px solid var(--border); + border-radius: 999px; + background: var(--input); +} +.res-seg-btn { + border: none; + background: transparent; + color: var(--muted); + font-family: inherit; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.02em; + padding: 4px 14px; + border-radius: 999px; + cursor: pointer; + transition: background 0.12s, color 0.12s; +} +.res-seg-btn:hover { + color: var(--ink); +} +.res-seg-btn.active { + background: var(--accent); + color: #fff; +} + +/* Schraffur-Swatch (kleine Vorschau in Liste + große im Detail). */ +.res-swatch { + border-radius: 3px; +} + /* ══ Panel-Chrome (Andockrahmen: Docks, Tabs, Panel-Rahmen) ═══════════════════ Der erweiterbare Rahmen: links/rechts je ein Dock mit Tab-Leiste und dem aktiven Panel. Greift dieselben Tokens wie die nav- und res-Klassen (dunkles DOSSIER- diff --git a/src/ui/ResourceManager.tsx b/src/ui/ResourceManager.tsx index 2936ca8..33fbd8d 100644 --- a/src/ui/ResourceManager.tsx +++ b/src/ui/ResourceManager.tsx @@ -42,6 +42,7 @@ import { } from "../materials/ambientcg"; import { requestMaterialPreview, cancelMaterialPreview } from "../materials/spherePreview"; import { EyeIcon } from "./EyeIcon"; +import { HatchSwatch, LineSwatch } from "./hatchPreview"; import { t } from "../i18n"; // ── Auswahl-Optionen (UI-Beschriftung übersetzt, Werte englisch) ─────────── @@ -210,6 +211,62 @@ function DeleteButton({ onClick, title }: { onClick: () => void; title: string } ); } +// ── Master-Detail-Bausteine (DOSSIER-Stil: Liste links, Detail rechts) ────── + +/** + * Segmentierter Umschalter (Pillen-Gruppe) für kleine, sich ausschließende + * Auswahlen — z. B. Schraffur-Typ Vektor/Bild oder Linien-Typ Strich/Zickzack. + */ +function Segmented({ + value, + onChange, + options, +}: { + value: T; + onChange: (v: T) => void; + options: { value: T; label: string }[]; +}) { + return ( +
+ {options.map((o) => ( + + ))} +
+ ); +} + +/** + * Eine beschriftete Detail-Zeile (Label links, Steuerelement rechts). Bewusst + * ein
(kein