Fang-Optionen und Display/Print-Umschalter in die Footerbar
Der Linien-Modus (Display/Print) wandert aus der Oberleiste und die Fang-Optionen aus der Werkzeug-Palette in die Statusleiste unten: - StatusBar: segmentierter Display/Print-Umschalter (Stil wie der Renderer-Umschalter) plus Fang-Pill mit nach oben öffnendem Popover (Master-Schalter + Endpunkt/Mittelpunkt/Schnittpunkt/Kante/Raster/ Ortho + Rastermass). - TopBar: Linien-Modus-Gruppe entfernt. - ToolsPanel: Fang-Abschnitt entfernt. - App reicht lineMode/snap an die StatusBar durch.
This commit is contained in:
+210
-1
@@ -8,7 +8,9 @@
|
||||
// Rein gesteuert (controlled): alle Werte kommen aus App (Modell + View-State),
|
||||
// keine eigene Logik. Bezeichner englisch, UI-Text/Kommentare deutsch.
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { Vec2 } from "../model/types";
|
||||
import type { SnapSettings } from "../tools/types";
|
||||
import { t } from "../i18n";
|
||||
|
||||
/** Plan-Renderer-Wahl, gespiegelt aus dem `?engine`-URL-Param (siehe PlanView). */
|
||||
@@ -41,6 +43,199 @@ export interface StatusBarProps {
|
||||
* Live-Remount der Renderer-Hooks).
|
||||
*/
|
||||
onRendererModeChange: (mode: RendererMode) => void;
|
||||
/** Linien-Darstellung: „display" (Haarlinien) oder „print" (echte mm-Stärken). */
|
||||
lineMode: "display" | "print";
|
||||
/** Linien-Darstellung wechseln. */
|
||||
onLineModeChange: (mode: "display" | "print") => void;
|
||||
/** Nur im Grundriss sinnvoll — deaktiviert beide Segmente wenn `false`. */
|
||||
lineModeEnabled: boolean;
|
||||
/** Fang-Einstellungen (Werkzeug-übergreifend). */
|
||||
snap: SnapSettings;
|
||||
/** Fang-Einstellungen ändern. */
|
||||
onSnapChange: (s: SnapSettings) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zweisegmentige Umschalt-Pille Display/Print für die Statusleiste — optisch
|
||||
* an `.sb-renderer-toggle` angelehnt (gleiche Höhe/Radius), eigene Klasse
|
||||
* `.sb-seg` weil sie generisch (nicht renderer-spezifisch) bleibt.
|
||||
*/
|
||||
function LineModeToggle({
|
||||
lineMode,
|
||||
onLineModeChange,
|
||||
lineModeEnabled,
|
||||
}: {
|
||||
lineMode: "display" | "print";
|
||||
onLineModeChange: (m: "display" | "print") => void;
|
||||
lineModeEnabled: boolean;
|
||||
}) {
|
||||
return (
|
||||
<span className="sb-field">
|
||||
<span className="sb-key">{t("topbar.lineModeGroup")}</span>
|
||||
<span
|
||||
className={`sb-seg${lineModeEnabled ? "" : " disabled"}`}
|
||||
role="group"
|
||||
aria-label={t("topbar.lineModeGroup")}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={`sb-seg-btn${lineMode === "display" ? " active" : ""}`}
|
||||
aria-pressed={lineMode === "display"}
|
||||
disabled={!lineModeEnabled}
|
||||
title={t("topbar.lineMode.display.hint")}
|
||||
onClick={() => onLineModeChange("display")}
|
||||
>
|
||||
{t("topbar.lineMode.display")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`sb-seg-btn${lineMode === "print" ? " active" : ""}`}
|
||||
aria-pressed={lineMode === "print"}
|
||||
disabled={!lineModeEnabled}
|
||||
title={t("topbar.lineMode.print.hint")}
|
||||
onClick={() => onLineModeChange("print")}
|
||||
>
|
||||
{t("topbar.lineMode.print")}
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fang-Kontrolle für die Statusleiste: eine Pille schaltet `snap.enabled`
|
||||
* direkt um (Klick = an/aus, aktive Einfärbung wie der Renderer-Umschalter);
|
||||
* ein kleiner Caret daneben öffnet ein NACH OBEN öffnendes Popover (Leiste
|
||||
* sitzt unten) mit den Fang-Unteroptionen — 1:1 aus dem früheren
|
||||
* ToolsPanel-„Fang-Abschnitt" übernommen. Schliesst bei Klick ausserhalb.
|
||||
*/
|
||||
function SnapControl({
|
||||
snap,
|
||||
onSnapChange,
|
||||
}: {
|
||||
snap: SnapSettings;
|
||||
onSnapChange: (s: SnapSettings) => void;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const wrapRef = useRef<HTMLSpanElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onDown = (e: MouseEvent) => {
|
||||
if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", onDown);
|
||||
return () => document.removeEventListener("mousedown", onDown);
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<span className="sb-snap-wrap" ref={wrapRef}>
|
||||
<span className={`sb-seg${open ? " open" : ""}`} role="group" aria-label={t("tool.snap")}>
|
||||
<button
|
||||
type="button"
|
||||
className={`sb-seg-btn${snap.enabled ? " active" : ""}`}
|
||||
aria-pressed={snap.enabled}
|
||||
title={t("tool.snap.hint")}
|
||||
onClick={() => onSnapChange({ ...snap, enabled: !snap.enabled })}
|
||||
>
|
||||
{t("tool.snap")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`sb-seg-btn sb-seg-caret${open ? " active" : ""}`}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open}
|
||||
aria-label={t("tool.snap")}
|
||||
title={t("tool.snap")}
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
>
|
||||
▴
|
||||
</button>
|
||||
</span>
|
||||
{open && (
|
||||
<div className="sb-snap-popover" role="dialog" aria-label={t("tool.snap")}>
|
||||
<label className="sb-snap-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={snap.enabled}
|
||||
onChange={(e) => onSnapChange({ ...snap, enabled: e.target.checked })}
|
||||
/>
|
||||
<span>{t("snap.enabled")}</span>
|
||||
</label>
|
||||
<label className="sb-snap-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={snap.endpoint}
|
||||
disabled={!snap.enabled}
|
||||
onChange={(e) => onSnapChange({ ...snap, endpoint: e.target.checked })}
|
||||
/>
|
||||
<span>{t("snap.endpoint")}</span>
|
||||
</label>
|
||||
<label className="sb-snap-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={snap.midpoint}
|
||||
disabled={!snap.enabled}
|
||||
onChange={(e) => onSnapChange({ ...snap, midpoint: e.target.checked })}
|
||||
/>
|
||||
<span>{t("snap.midpoint")}</span>
|
||||
</label>
|
||||
<label className="sb-snap-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={snap.intersection}
|
||||
disabled={!snap.enabled}
|
||||
onChange={(e) =>
|
||||
onSnapChange({ ...snap, intersection: e.target.checked })
|
||||
}
|
||||
/>
|
||||
<span>{t("snap.intersection")}</span>
|
||||
</label>
|
||||
<label className="sb-snap-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={snap.onEdge}
|
||||
disabled={!snap.enabled}
|
||||
onChange={(e) => onSnapChange({ ...snap, onEdge: e.target.checked })}
|
||||
/>
|
||||
<span>{t("snap.onEdge")}</span>
|
||||
</label>
|
||||
<label className="sb-snap-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={snap.grid}
|
||||
disabled={!snap.enabled}
|
||||
onChange={(e) => onSnapChange({ ...snap, grid: e.target.checked })}
|
||||
/>
|
||||
<span>{t("snap.grid")}</span>
|
||||
</label>
|
||||
<label className="sb-snap-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={snap.ortho}
|
||||
disabled={!snap.enabled}
|
||||
onChange={(e) => onSnapChange({ ...snap, ortho: e.target.checked })}
|
||||
/>
|
||||
<span>{t("snap.ortho")}</span>
|
||||
</label>
|
||||
<label className="sb-snap-row sb-snap-row-num">
|
||||
<span>{t("snap.gridSize")}</span>
|
||||
<input
|
||||
type="number"
|
||||
step={0.05}
|
||||
min={0.01}
|
||||
value={snap.gridSize}
|
||||
onChange={(e) =>
|
||||
onSnapChange({ ...snap, gridSize: Number(e.target.value) })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/** Ein Wert-Feld im rechten Cluster: kleine Beschriftung + Wert. */
|
||||
@@ -72,6 +267,11 @@ export function StatusBar({
|
||||
rendererMode,
|
||||
webGpuAvailable,
|
||||
onRendererModeChange,
|
||||
lineMode,
|
||||
onLineModeChange,
|
||||
lineModeEnabled,
|
||||
snap,
|
||||
onSnapChange,
|
||||
}: StatusBarProps) {
|
||||
const coords = cursor
|
||||
? `${fmt(cursor.x)}, ${fmt(cursor.y)}`
|
||||
@@ -82,8 +282,17 @@ export function StatusBar({
|
||||
{/* Links: Werkzeug-Hinweis. */}
|
||||
<span className="sb-hint">{hint}</span>
|
||||
|
||||
{/* Rechts: abgeleiteter Wert-Cluster. */}
|
||||
{/* Rechts: abgeleiteter Wert-Cluster. Interaktive Kontrollen (Linien-
|
||||
Darstellung, Fang) zuerst, danach die reinen Anzeigewerte. */}
|
||||
<div className="sb-cluster">
|
||||
<LineModeToggle
|
||||
lineMode={lineMode}
|
||||
onLineModeChange={onLineModeChange}
|
||||
lineModeEnabled={lineModeEnabled}
|
||||
/>
|
||||
<Sep />
|
||||
<SnapControl snap={snap} onSnapChange={onSnapChange} />
|
||||
<Sep />
|
||||
<span className="sb-field">
|
||||
<span className="sb-key">{t("status.renderer")}</span>
|
||||
<span className="sb-renderer-toggle" role="group" aria-label={t("status.renderer")}>
|
||||
|
||||
@@ -141,12 +141,6 @@ export interface TopBarProps {
|
||||
/** Referenzlinien nur im Grundriss sinnvoll (sonst deaktiviert). */
|
||||
referenceLinesEnabled: boolean;
|
||||
|
||||
// Linien-Darstellung: Display (Haarlinien) ↔ Print (echte mm-Strichstärken).
|
||||
lineMode: "display" | "print";
|
||||
onLineMode: (m: "display" | "print") => void;
|
||||
/** Nur im Grundriss sinnvoll. */
|
||||
lineModeEnabled: boolean;
|
||||
|
||||
// Sichtbarkeits-Kombinationen (Ebenen-/Zeichnungskombinationen).
|
||||
/** Handler-Satz für die beiden Kombinations-Dropdowns. */
|
||||
combos: ComboMenuHandlers;
|
||||
@@ -855,9 +849,6 @@ export function TopBar({
|
||||
referenceLines,
|
||||
onReferenceLines,
|
||||
referenceLinesEnabled,
|
||||
lineMode,
|
||||
onLineMode,
|
||||
lineModeEnabled,
|
||||
combos,
|
||||
resourcesOpen,
|
||||
onToggleResources,
|
||||
@@ -1202,30 +1193,6 @@ export function TopBar({
|
||||
|
||||
<Sep />
|
||||
|
||||
{/* Linien-Darstellung: Display (alle Haarlinien) ↔ Print (echte mm-
|
||||
Strichstärken). Eine Einfach-Auswahl → als Dropdown (Trigger-Pill +
|
||||
Häkchen-Liste, wie Detailgrad/Massstab), nicht mehr als Segment-Pillen.
|
||||
Optionen tragen ein Icon plus Klartext. Nur im Grundriss sinnvoll. */}
|
||||
<div className="tb-group" role="group" aria-label={t("topbar.lineModeGroup")}>
|
||||
<label className="tb-field">
|
||||
<span className="tb-label">{t("topbar.lineModeGroup")}</span>
|
||||
<Dropdown
|
||||
value={lineMode}
|
||||
disabled={!lineModeEnabled}
|
||||
onChange={(v) => onLineMode(v as "display" | "print")}
|
||||
title={
|
||||
lineModeEnabled
|
||||
? `${t("topbar.lineMode.display.hint")} · ${t("topbar.lineMode.print.hint")}`
|
||||
: t("topbar.lineModeGroup")
|
||||
}
|
||||
options={[
|
||||
{ value: "display", label: t("topbar.lineMode.display") },
|
||||
{ value: "print", label: t("topbar.lineMode.print") },
|
||||
]}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Rechte Gruppe: Layout-Menü · Ressourcen · Projektname. */}
|
||||
<div className="topbar-right">
|
||||
{layoutMenu}
|
||||
|
||||
Reference in New Issue
Block a user