b9e330ecb7
- Schnittebenen: 3D-Live-Schnitt folgt der gewählten Grundriss-Schnittlinie (section3dCutId/sectionPlaneFromLevel), Schalter "Im 3D schneiden" im Objekt-Info, Doppelklick auf Schnittlinie springt in 2D-Schnittansicht, unsichtbare Ebenen blenden ihre Führungslinie aus - Eigene native Tauri-Fenster für Kontext-Import/Zeichnungsebenen/ Ebenen-Einstellungen/Ressourcen/Einstellungen + klassische Menüleiste (AppMenuBar) neben der Wortmarke - Hell/Dunkel-Umschalter (Einstellungen → Darstellung), persistiert, flackerfrei vor erstem Render gesetzt - SWISSIMAGE-Luftbild-Import (swisstopo WMS) als Kontext-Hintergrundebene - UI-Politur: Werkzeug-Panel Symbole/Liste umschaltbar, Topbar-Quick-Access- Icons entfernt, Zahnrad→Einstellungen in Panel-Köpfen, Footerbar/ Snap-Marker/Maß-HUD auf helle Pillen-Sprache umgestellt - Neues Dachziegel-Material (RoofingTiles013A)
292 lines
10 KiB
TypeScript
292 lines
10 KiB
TypeScript
// Inhalts-Panel „Werkzeuge" — die Werkzeug-Palette (ArchiCAD-/Vectorworks-Stil).
|
||
// Oben ein Umschalter „Liste ↔ Symbole" (Icon+Name als Zeilen ODER kompaktes
|
||
// Icon-Raster). Darunter drei einheitlich gestylte Rubriken: 2D (Zeichnen),
|
||
// 3D (BIM-Bauteile) und Bearbeiten (Ändern-Aktionen) — Werkzeuge UND Befehle
|
||
// teilen denselben Knopf-Look (`.tp-item`), damit „Ändern" nicht mehr optisch
|
||
// aus der Reihe fällt.
|
||
//
|
||
// Daten/Handler kommen über usePanelHost (kein Prop-Drilling). Werkzeug-Metadaten
|
||
// aus der Tools-Registry (getTool), Befehls-Labels aus der Command-Registry.
|
||
//
|
||
// Bezeichner englisch, UI-Text/Kommentare deutsch (CONVENTIONS.md). Alle sichtbaren
|
||
// Texte über t(...).
|
||
|
||
import { useState } from "react";
|
||
import { t } from "../i18n";
|
||
import { usePanelHost } from "./host";
|
||
import type { ToolId } from "./host";
|
||
import { getTool } from "../tools/tools";
|
||
import { getCommand } from "../commands/registry";
|
||
import { CommandIcon } from "../ui/ribbon/CommandIcon";
|
||
|
||
// ── Icons ──────────────────────────────────────────────────────────────────
|
||
// Keine echten Asset-Icons vorhanden: schlichte Inline-SVG-Glyphen je Werkzeug
|
||
// (~16×16, stroke=currentColor, fill=none), damit das Icon die Textfarbe der
|
||
// Zeile übernimmt.
|
||
export function ToolIcon({ id }: { id: ToolId }) {
|
||
const common = {
|
||
width: 16,
|
||
height: 16,
|
||
viewBox: "0 0 16 16",
|
||
fill: "none",
|
||
stroke: "currentColor",
|
||
strokeWidth: 1.4,
|
||
strokeLinecap: "round" as const,
|
||
strokeLinejoin: "round" as const,
|
||
};
|
||
switch (id) {
|
||
case "select":
|
||
// Pfeil-Cursor (klassischer Zeiger mit Schweif).
|
||
return (
|
||
<svg {...common} fill="currentColor" stroke="currentColor" strokeWidth={0.8}>
|
||
<path d="M4 2.5 L4 13.2 L6.9 10.4 L8.9 14.6 L10.7 13.8 L8.7 9.7 L12.6 9.6 Z" />
|
||
</svg>
|
||
);
|
||
case "wall":
|
||
// Wandband: zwei parallele Kanten mit geschlossenen Enden (Wandstück).
|
||
return (
|
||
<svg {...common}>
|
||
<rect x="2" y="5.5" width="12" height="5" />
|
||
</svg>
|
||
);
|
||
case "ceiling":
|
||
// Deckenplatte: gefülltes Rechteck mit Diagonal-Schraffur (Slab-Symbol).
|
||
return (
|
||
<svg {...common}>
|
||
<rect x="2.5" y="4" width="11" height="8" />
|
||
<line x1="4" y1="12" x2="8" y2="4" />
|
||
<line x1="8" y1="12" x2="12" y2="4" />
|
||
</svg>
|
||
);
|
||
case "window":
|
||
// Fenster (SIA-Aufsicht): Wandöffnung mit doppelter Glaslinie.
|
||
return (
|
||
<svg {...common}>
|
||
<line x1="2" y1="5.5" x2="14" y2="5.5" />
|
||
<line x1="2" y1="10.5" x2="14" y2="10.5" />
|
||
<line x1="2" y1="8" x2="14" y2="8" />
|
||
<line x1="2" y1="5.5" x2="2" y2="10.5" />
|
||
<line x1="14" y1="5.5" x2="14" y2="10.5" />
|
||
</svg>
|
||
);
|
||
case "door":
|
||
// Tür (SIA-Aufsicht): Türblatt aufrecht + Viertelkreis-Schwenkbogen.
|
||
return (
|
||
<svg {...common}>
|
||
<line x1="3.5" y1="13" x2="3.5" y2="3" />
|
||
<path d="M3.5 3 A9.5 9.5 0 0 1 13 12.5" strokeDasharray="1.4 1.4" />
|
||
<line x1="3.5" y1="13" x2="13" y2="13" />
|
||
</svg>
|
||
);
|
||
case "stair":
|
||
// Treppe (SIA-Aufsicht): Wangen, gleichmäßige Tritte, Lauflinie mit
|
||
// Aufwärts-Pfeil (Idee: Vollbogen ersetzt durch eine echte Treppe).
|
||
return (
|
||
<svg {...common}>
|
||
<rect x="3" y="2" width="10" height="12" />
|
||
<line x1="3" y1="4.4" x2="13" y2="4.4" />
|
||
<line x1="3" y1="6.8" x2="13" y2="6.8" />
|
||
<line x1="3" y1="9.2" x2="13" y2="9.2" />
|
||
<line x1="3" y1="11.6" x2="13" y2="11.6" />
|
||
<line x1="8" y1="13.2" x2="8" y2="3.4" />
|
||
<polyline points="6.3,5.1 8,3.2 9.7,5.1" />
|
||
</svg>
|
||
);
|
||
case "room":
|
||
// Raum: umschlossene Fläche (Rechteck) mit Flächen-Marke (kleines Kreuz).
|
||
return (
|
||
<svg {...common}>
|
||
<rect x="2.5" y="3.5" width="11" height="9" />
|
||
<line x1="6" y1="8" x2="10" y2="8" />
|
||
<line x1="8" y1="6" x2="8" y2="10" />
|
||
</svg>
|
||
);
|
||
case "line":
|
||
// Einzelne Diagonale.
|
||
return (
|
||
<svg {...common}>
|
||
<line x1="3" y1="13" x2="13" y2="3" />
|
||
</svg>
|
||
);
|
||
case "polyline":
|
||
// Dreisegmentiger Zickzack.
|
||
return (
|
||
<svg {...common}>
|
||
<polyline points="2,12 6,5 10,11 14,4" />
|
||
</svg>
|
||
);
|
||
case "rect":
|
||
// Rechteck-Umriss.
|
||
return (
|
||
<svg {...common}>
|
||
<rect x="2.5" y="3.5" width="11" height="9" />
|
||
</svg>
|
||
);
|
||
case "circle":
|
||
// Kreis-Umriss.
|
||
return (
|
||
<svg {...common}>
|
||
<circle cx="8" cy="8" r="5.5" />
|
||
</svg>
|
||
);
|
||
case "arc":
|
||
// Offener Bogen (gut 180°) mit Endpunkt-Markierungen.
|
||
return (
|
||
<svg {...common}>
|
||
<path d="M 2.5 11 A 5.6 5.6 0 0 1 13.5 11" fill="none" />
|
||
<circle cx="2.5" cy="11" r="0.9" fill="currentColor" stroke="none" />
|
||
<circle cx="13.5" cy="11" r="0.9" fill="currentColor" stroke="none" />
|
||
</svg>
|
||
);
|
||
case "text":
|
||
// Serifen-„A" als Text-Symbol.
|
||
return (
|
||
<svg {...common}>
|
||
<path d="M 4 13 L 8 3 L 12 13" fill="none" />
|
||
<path d="M 5.5 9.5 L 10.5 9.5" fill="none" />
|
||
</svg>
|
||
);
|
||
case "textbox":
|
||
// Spalten-Rahmen mit drei Textzeilen (Absatztext).
|
||
return (
|
||
<svg {...common}>
|
||
<rect x="2.5" y="3" width="11" height="10" fill="none" />
|
||
<line x1="4.5" y1="6" x2="11.5" y2="6" />
|
||
<line x1="4.5" y1="8.5" x2="11.5" y2="8.5" />
|
||
<line x1="4.5" y1="11" x2="9" y2="11" />
|
||
</svg>
|
||
);
|
||
default:
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// ── Kategorien ───────────────────────────────────────────────────────────────
|
||
// Werkzeuge/Befehle in drei Rubriken sortiert (Nutzerwunsch: „sortiere nach 2D
|
||
// und 3D und Bearbeiten"). Befehlsnamen spiegeln die Ribbon-Registry
|
||
// (ui/ribbon/ribbonItems.ts) — dort liegt die kanonische Liste; hier nur die
|
||
// Panel-Gruppierung (2D-Zeichnen · 3D/BIM-Bauteile · Bearbeiten-Aktionen).
|
||
const TOOLS_2D: ToolId[] = ["line", "polyline", "rect", "circle", "arc", "text", "textbox"];
|
||
const TOOLS_3D: ToolId[] = ["wall", "ceiling", "window", "door", "stair", "room"];
|
||
const COMMANDS_3D = ["column", "ceilingopening", "roof", "extrude", "sectionline", "viewline"];
|
||
const COMMANDS_EDIT = ["move", "copy", "rotate", "mirror", "offset", "trim", "join", "measure"];
|
||
|
||
type ToolsView = "list" | "icons";
|
||
const VIEW_STORAGE_KEY = "dossier.toolsPanel.view";
|
||
|
||
function loadView(): ToolsView {
|
||
try {
|
||
return localStorage.getItem(VIEW_STORAGE_KEY) === "icons" ? "icons" : "list";
|
||
} catch {
|
||
return "list";
|
||
}
|
||
}
|
||
|
||
// ── Panel ────────────────────────────────────────────────────────────────────
|
||
|
||
export function ToolsPanel() {
|
||
const host = usePanelHost();
|
||
const [view, setView] = useState<ToolsView>(loadView);
|
||
|
||
const chooseView = (v: ToolsView) => {
|
||
setView(v);
|
||
try {
|
||
localStorage.setItem(VIEW_STORAGE_KEY, v);
|
||
} catch {
|
||
/* Persistenz ist Best-Effort (privater Modus o. ä.). */
|
||
}
|
||
};
|
||
|
||
// Ein Werkzeug-Knopf (Icon + Name). Boden-gebundene (`floorOnly`) Werkzeuge
|
||
// sind ohne aktives Geschoss gesperrt; freie 2D-Werkzeuge bleiben nutzbar.
|
||
const toolButton = (id: ToolId) => {
|
||
const tool = getTool(id);
|
||
const active = host.activeTool === id;
|
||
const disabled = !!tool.floorOnly && !host.toolsEnabled;
|
||
const label = t(tool.labelKey);
|
||
const title = disabled ? t("tool.floorOnlyDisabled") : t(tool.hintKey(tool.init()));
|
||
return (
|
||
<button
|
||
key={id}
|
||
type="button"
|
||
className={"tp-item" + (active ? " active" : "") + (disabled ? " disabled" : "")}
|
||
title={view === "icons" ? label : title}
|
||
disabled={disabled}
|
||
onClick={() => host.onSelectTool(id)}
|
||
>
|
||
<ToolIcon id={id} />
|
||
<span className="tp-label">{label}</span>
|
||
</button>
|
||
);
|
||
};
|
||
|
||
// Ein Befehls-Knopf (Ändern-/BIM-Kommando ohne eigenen Werkzeug-Modus) —
|
||
// gleicher Look wie die Werkzeuge (Nutzerwunsch: nicht „ganz anders").
|
||
const commandButton = (name: string) => {
|
||
const cmd = getCommand(name);
|
||
const label = cmd ? t(cmd.labelKey) : t(`cmd.${name}.label`);
|
||
const active = host.activeCommand === name;
|
||
return (
|
||
<button
|
||
key={name}
|
||
type="button"
|
||
className={"tp-item" + (active ? " active" : "")}
|
||
title={label}
|
||
onClick={() => host.onRunCommand(name)}
|
||
>
|
||
<CommandIcon name={name} />
|
||
<span className="tp-label">{label}</span>
|
||
</button>
|
||
);
|
||
};
|
||
|
||
const bodyClass = "tp-body" + (view === "icons" ? " icons" : "");
|
||
|
||
return (
|
||
<div className="tools-panel">
|
||
{/* Anzeige-Umschalter: nur Symbole ODER Symbol + Name (Liste). */}
|
||
<div className="tp-head">
|
||
<div className="tp-viewtoggle" role="group" aria-label={t("tools.view.label")}>
|
||
<button
|
||
type="button"
|
||
className={"tp-viewbtn" + (view === "list" ? " active" : "")}
|
||
aria-pressed={view === "list"}
|
||
onClick={() => chooseView("list")}
|
||
>
|
||
{t("tools.view.list")}
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className={"tp-viewbtn" + (view === "icons" ? " active" : "")}
|
||
aria-pressed={view === "icons"}
|
||
onClick={() => chooseView("icons")}
|
||
>
|
||
{t("tools.view.icons")}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Auswahl-Werkzeug: immer als volle Zeile zuoberst (meistgenutzt). */}
|
||
<div className="tp-select">{toolButton("select")}</div>
|
||
|
||
<section className="tp-section">
|
||
<div className="tp-section-title">{t("tools.cat.2d")}</div>
|
||
<div className={bodyClass}>{TOOLS_2D.map(toolButton)}</div>
|
||
</section>
|
||
|
||
<section className="tp-section">
|
||
<div className="tp-section-title">{t("tools.cat.3d")}</div>
|
||
<div className={bodyClass}>
|
||
{TOOLS_3D.map(toolButton)}
|
||
{COMMANDS_3D.map(commandButton)}
|
||
</div>
|
||
</section>
|
||
|
||
<section className="tp-section">
|
||
<div className="tp-section-title">{t("tools.cat.edit")}</div>
|
||
<div className={bodyClass}>{COMMANDS_EDIT.map(commandButton)}</div>
|
||
</section>
|
||
</div>
|
||
);
|
||
}
|