Renderer-Umschalter in die Settings; Nordstern als Default

Der WebGL2<->Nordstern-Umschalter wandert aus der Statusleiste in den
Settings-Dialog. Default ist jetzt Nordstern (WASM), sofern WebGPU
verfuegbar ist; WebGL2 nur noch als expliziter Fallback. So sieht man
nicht mehr versehentlich die Darstellungsfehler des WebGL2-Viewers.
styles.css: .sb-renderer-toggle align-self:flex-start, damit das Pill
im Settings-Dialog nicht auf volle Breite zieht.
This commit is contained in:
2026-07-04 13:47:27 +02:00
parent 8547d38e9a
commit 8c96a5dfb9
4 changed files with 55 additions and 46 deletions
+10 -7
View File
@@ -2534,15 +2534,18 @@ export default function App() {
// Persistiert in localStorage, damit die Wahl einen Neustart ohne URL-Param // Persistiert in localStorage, damit die Wahl einen Neustart ohne URL-Param
// übersteht (z.B. die Electron-App startet immer auf der Basis-URL). // übersteht (z.B. die Electron-App startet immer auf der Basis-URL).
const RENDERER_STORAGE_KEY = "cad.rendererMode"; const RENDERER_STORAGE_KEY = "cad.rendererMode";
const webGpuAvailable = typeof navigator !== "undefined" && "gpu" in navigator;
const rendererMode: RendererMode = (() => { const rendererMode: RendererMode = (() => {
const fromUrl = new URLSearchParams(window.location.search).get("engine"); const fromUrl = new URLSearchParams(window.location.search).get("engine");
if (fromUrl === "wasm") return "wasm"; if (fromUrl === "wasm") return "wasm";
if (fromUrl === "webgl2") return "webgl2"; if (fromUrl === "webgl2") return "webgl2";
return window.localStorage.getItem(RENDERER_STORAGE_KEY) === "wasm" const fromStorage = window.localStorage.getItem(RENDERER_STORAGE_KEY);
? "wasm" if (fromStorage === "webgl2") return "webgl2";
: "webgl2"; if (fromStorage === "wasm") return "wasm";
// Keine explizite Wahl (weder URL noch localStorage) — Nordstern (WASM)
// ist Standard, sofern WebGPU verfügbar ist; sonst WebGL2 als Fallback.
return webGpuAvailable ? "wasm" : "webgl2";
})(); })();
const webGpuAvailable = typeof navigator !== "undefined" && "gpu" in navigator;
const setRendererMode = (mode: RendererMode) => { const setRendererMode = (mode: RendererMode) => {
window.localStorage.setItem(RENDERER_STORAGE_KEY, mode); window.localStorage.setItem(RENDERER_STORAGE_KEY, mode);
const url = new URL(window.location.href); const url = new URL(window.location.href);
@@ -3343,9 +3346,6 @@ export default function App() {
referenceLinesEnabled={planActive} referenceLinesEnabled={planActive}
activeFloor={activeLevel.name} activeFloor={activeLevel.name}
activeLayer={activeLayerName} activeLayer={activeLayerName}
rendererMode={rendererMode}
webGpuAvailable={webGpuAvailable}
onRendererModeChange={setRendererMode}
lineMode={lineMode} lineMode={lineMode}
onLineModeChange={setLineMode} onLineModeChange={setLineMode}
lineModeEnabled={planActive} lineModeEnabled={planActive}
@@ -3411,6 +3411,9 @@ export default function App() {
onSnapColorChange={setSnapColor} onSnapColorChange={setSnapColor}
project={project} project={project}
setProject={setProject} setProject={setProject}
rendererMode={rendererMode}
webGpuAvailable={webGpuAvailable}
onRendererModeChange={setRendererMode}
onClose={() => setSettingsOpen(false)} onClose={() => setSettingsOpen(false)}
/> />
)} )}
+5 -3
View File
@@ -816,12 +816,14 @@ body {
background: var(--border); background: var(--border);
} }
/* Renderer-Umschalter (WebGL2 / Engine) in der Statusleiste — kleine /* Renderer-Umschalter (WebGL2 / Nordstern) — kleine Segmentpille; ursprünglich
Segmentpille, an die 22px-Leiste angepasst (siehe .tb-seg für das größere in der Statusleiste (22px-Leiste), jetzt im Einstellungs-Fenster. In einer
Vorbild in der Oberleiste). */ flex-column (.imp-section) würde sie sonst auf volle Breite gestreckt
werden (Standard-`align-items: stretch`) — bleibt daher inhaltsbreit. */
.sb-renderer-toggle { .sb-renderer-toggle {
display: inline-flex; display: inline-flex;
align-items: stretch; align-items: stretch;
align-self: flex-start;
height: 16px; height: 16px;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 999px; border-radius: 999px;
+40
View File
@@ -14,6 +14,7 @@ import { t } from "../i18n";
import { ACCENT_SWATCHES } from "../theme/accents"; import { ACCENT_SWATCHES } from "../theme/accents";
import type { Project } from "../model/types"; import type { Project } from "../model/types";
import { ColorHexField } from "./ColorHexField"; import { ColorHexField } from "./ColorHexField";
import type { RendererMode } from "./StatusBar";
export interface SettingsDialogProps { export interface SettingsDialogProps {
marqueeColor: string; marqueeColor: string;
@@ -22,6 +23,15 @@ export interface SettingsDialogProps {
onSnapColorChange: (hex: string) => void; onSnapColorChange: (hex: string) => void;
project: Project; project: Project;
setProject: (next: Project | ((p: Project) => Project)) => void; setProject: (next: Project | ((p: Project) => Project)) => void;
/** Aktuell gewählter Plan-Renderer (aus dem `?engine`-URL-Param abgeleitet). */
rendererMode: RendererMode;
/** Ist `navigator.gpu` vorhanden? Steuert, ob „Nordstern" wählbar ist. */
webGpuAvailable: boolean;
/**
* Renderer wechseln (setzt/entfernt den `engine`-URL-Param und lädt neu kein
* Live-Remount der Renderer-Hooks).
*/
onRendererModeChange: (mode: RendererMode) => void;
onClose: () => void; onClose: () => void;
} }
@@ -32,6 +42,9 @@ export function SettingsDialog({
onSnapColorChange, onSnapColorChange,
project, project,
setProject, setProject,
rendererMode,
webGpuAvailable,
onRendererModeChange,
onClose, onClose,
}: SettingsDialogProps) { }: SettingsDialogProps) {
useEffect(() => { useEffect(() => {
@@ -85,6 +98,33 @@ export function SettingsDialog({
</header> </header>
<div className="imp-body"> <div className="imp-body">
<section className="imp-section">
<div className="imp-section-title">{t("status.renderer")}</div>
<span className="sb-renderer-toggle" role="group" aria-label={t("status.renderer")}>
<button
type="button"
className={`sb-renderer-btn${rendererMode === "webgl2" ? " active" : ""}`}
aria-pressed={rendererMode === "webgl2"}
onClick={() => onRendererModeChange("webgl2")}
>
{t("status.rendererWebgl")}
</button>
<button
type="button"
className={`sb-renderer-btn${rendererMode === "wasm" ? " active" : ""}`}
aria-pressed={rendererMode === "wasm"}
disabled={!webGpuAvailable}
title={webGpuAvailable ? undefined : t("status.rendererWasmUnavailable")}
onClick={() => onRendererModeChange("wasm")}
>
{t("status.rendererWasm")}
</button>
</span>
{!webGpuAvailable && (
<div className="imp-note">{t("status.rendererWasmUnavailable")}</div>
)}
</section>
<section className="imp-section"> <section className="imp-section">
<div className="imp-section-title">{t("settings.section.display")}</div> <div className="imp-section-title">{t("settings.section.display")}</div>
<AccentColorField <AccentColorField
-36
View File
@@ -33,15 +33,6 @@ export interface StatusBarProps {
activeFloor: string; activeFloor: string;
/** Name der aktiven Ebene (Kategorie) oder „—". */ /** Name der aktiven Ebene (Kategorie) oder „—". */
activeLayer: string; activeLayer: string;
/** Aktuell gewählter Plan-Renderer (aus dem `?engine`-URL-Param abgeleitet). */
rendererMode: RendererMode;
/** Ist `navigator.gpu` vorhanden? Steuert, ob „Engine" wählbar ist. */
webGpuAvailable: boolean;
/**
* Renderer wechseln (setzt/entfernt den `engine`-URL-Param und lädt neu kein
* Live-Remount der Renderer-Hooks).
*/
onRendererModeChange: (mode: RendererMode) => void;
/** Linien-Darstellung: „display" (Haarlinien) oder „print" (echte mm-Stärken). */ /** Linien-Darstellung: „display" (Haarlinien) oder „print" (echte mm-Stärken). */
lineMode: "display" | "print"; lineMode: "display" | "print";
/** Linien-Darstellung wechseln. */ /** Linien-Darstellung wechseln. */
@@ -264,9 +255,6 @@ export function StatusBar({
referenceLinesEnabled, referenceLinesEnabled,
activeFloor, activeFloor,
activeLayer, activeLayer,
rendererMode,
webGpuAvailable,
onRendererModeChange,
lineMode, lineMode,
onLineModeChange, onLineModeChange,
lineModeEnabled, lineModeEnabled,
@@ -309,30 +297,6 @@ export function StatusBar({
{t("status.referenceLines")} {t("status.referenceLines")}
</button> </button>
<Sep /> <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")}>
<button
type="button"
className={`sb-renderer-btn${rendererMode === "webgl2" ? " active" : ""}`}
aria-pressed={rendererMode === "webgl2"}
onClick={() => onRendererModeChange("webgl2")}
>
{t("status.rendererWebgl")}
</button>
<button
type="button"
className={`sb-renderer-btn${rendererMode === "wasm" ? " active" : ""}`}
aria-pressed={rendererMode === "wasm"}
disabled={!webGpuAvailable}
title={webGpuAvailable ? undefined : t("status.rendererWasmUnavailable")}
onClick={() => onRendererModeChange("wasm")}
>
{t("status.rendererWasm")}
</button>
</span>
</span>
<Sep />
<Field label={t("status.xy")} value={coords} /> <Field label={t("status.xy")} value={coords} />
<Sep /> <Sep />
<Field label={t("status.unit")} value={unit} /> <Field label={t("status.unit")} value={unit} />