Files
DOSSIER-STANDALONE/src/native/SettingsWindowApp.tsx
T
karim 68a0459d0e Schnittebenen 2D↔3D Phase 1+2, native Fenster, Hell/Dunkel-Theme, SWISSIMAGE-Import
- 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)
2026-07-20 10:51:37 +02:00

106 lines
3.4 KiB
TypeScript

// Eigenständiger React-Root für das native Einstellungs-Fenster (siehe
// settingsWindow.ts für die Hauptfenster-Seite der Sync-Brücke). Von
// main.tsx gerendert, wenn die URL `?window=settings` trägt. Gleiches Prinzip
// wie ResourceWindowApp.tsx, nur mit dem schmaleren Settings-Zustand statt
// des ganzen Projekts.
//
// Bezeichner englisch, UI-Text über i18n (CONVENTIONS.md).
import { useEffect, useMemo, useState } from "react";
import { SettingsDialog } from "../ui/SettingsDialog";
import type { SettingsCommandHandlers, SettingsSyncState } from "./settingsWindow";
import { t } from "../i18n";
import { applyThemeMode } from "../theme/themeMode";
type HandlerName = keyof SettingsCommandHandlers;
export function SettingsWindowApp() {
const [state, setState] = useState<SettingsSyncState | null>(null);
// Dieses Fenster ist ein eigenes Dokument — Hell/Dunkel muss hier separat
// angewendet werden, sobald der synchronisierte Zustand eintrifft.
useEffect(() => {
if (state) applyThemeMode(state.themeMode);
}, [state]);
useEffect(() => {
let disposed = false;
const unlisten: Array<() => void> = [];
void (async () => {
const { listen, emitTo } = await import("@tauri-apps/api/event");
if (disposed) return;
unlisten.push(
await listen<SettingsSyncState>("settings-sync-state", (e) => setState(e.payload)),
);
// Hauptfenster um den aktuellen Stand bitten (Race-Schutz: dieses
// Fenster könnte nach dem letzten Sync-Push gestartet sein).
await emitTo("main", "settings-window-ready").catch(() => {});
})();
return () => {
disposed = true;
unlisten.forEach((u) => u());
};
}, []);
// Jeder Handler-Aufruf → EIN „settings-command"-Event ans Hauptfenster
// (Name + Argumente), analog ResourceWindowApp.tsx.
const handlers = useMemo<SettingsCommandHandlers>(
() =>
new Proxy({} as SettingsCommandHandlers, {
get(_target, prop: string) {
return (...args: unknown[]) => {
void import("@tauri-apps/api/event").then(({ emitTo }) =>
emitTo("main", "settings-command", { name: prop as HandlerName, args }).catch(
() => {},
),
);
};
},
}),
[],
);
const onClose = () => {
void import("@tauri-apps/api/window").then(({ getCurrentWindow }) =>
getCurrentWindow().close(),
);
};
if (!state) {
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100vh",
color: "var(--ink-dim, #8a8a8a)",
fontSize: 13,
}}
>
{t("resources.loading")}
</div>
);
}
return (
<SettingsDialog
themeMode={state.themeMode}
onThemeModeChange={handlers.onThemeModeChange}
marqueeColor={state.marqueeColor}
onMarqueeColorChange={handlers.onMarqueeColorChange}
snapColor={state.snapColor}
onSnapColorChange={handlers.onSnapColorChange}
referenceElevationMasl={state.referenceElevationMasl}
onReferenceElevationChange={handlers.onReferenceElevationChange}
rendererMode={state.rendererMode}
webGpuAvailable={state.webGpuAvailable}
onRendererModeChange={handlers.onRendererModeChange}
layout={state.layout}
onApplyLayout={handlers.onApplyLayout}
onClose={onClose}
windowed
/>
);
}