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)
This commit is contained in:
2026-07-20 10:51:37 +02:00
parent 0ab0361e3d
commit 68a0459d0e
90 changed files with 7730 additions and 1849 deletions
+112
View File
@@ -0,0 +1,112 @@
// Einstellungen als EIGENES natives Tauri-Fenster (statt Overlay-Dialog im
// Hauptfenster) — Hauptfenster-Seite der Brücke. Gleiches Prinzip wie
// resourceWindow.ts, aber schmaler: SettingsDialog braucht nur eine Handvoll
// einzelner Felder (nicht das ganze Project), daher ein explizites
// Sync-/Command-Paar statt eines generischen Proxys über 27 Handler.
//
// Hauptfenster → Einstellungs-Fenster: „settings-sync-state" mit
// { marqueeColor, snapColor, referenceElevationMasl, rendererMode,
// webGpuAvailable, layout } bei jeder Änderung + auf „settings-window-ready".
// Einstellungs-Fenster → Hauptfenster: „settings-command" mit
// { name, args } — dieselbe {name,args}-RPC-Form wie resource-command.
//
// Bezeichner englisch, Kommentare deutsch (CONVENTIONS.md).
import { useEffect, useRef } from "react";
import { isTauriRuntime } from "../io/projectFile";
import type { LayoutState } from "../panels/types";
import type { RendererMode } from "../ui/StatusBar";
import type { ThemeMode } from "../theme/themeMode";
import { BORDERLESS_WINDOW_CHROME } from "./windowChrome";
const SETTINGS_WINDOW_LABEL = "settings";
/** Alles, was SettingsDialog zum Rendern braucht — wandert 1:1 rüber. */
export interface SettingsSyncState {
themeMode: ThemeMode;
marqueeColor: string;
snapColor: string;
referenceElevationMasl: number | undefined;
rendererMode: RendererMode;
webGpuAvailable: boolean;
layout: LayoutState;
}
/** Alle Aktionen, die das Einstellungs-Fenster auslösen kann. */
export interface SettingsCommandHandlers {
onThemeModeChange: (mode: ThemeMode) => void;
onMarqueeColorChange: (hex: string) => void;
onSnapColorChange: (hex: string) => void;
onReferenceElevationChange: (n: number | undefined) => void;
onRendererModeChange: (mode: RendererMode) => void;
onApplyLayout: (layout: LayoutState) => void;
}
/** Öffnet das native Einstellungs-Fenster (bzw. holt es nach vorne). */
export async function openSettingsWindowNative(): Promise<void> {
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
const existing = await WebviewWindow.getByLabel(SETTINGS_WINDOW_LABEL);
if (existing) {
await existing.setFocus();
return;
}
new WebviewWindow(SETTINGS_WINDOW_LABEL, {
url: "index.html?window=settings",
title: "Einstellungen — Dossier",
width: 520,
height: 640,
minWidth: 420,
minHeight: 420,
...BORDERLESS_WINDOW_CHROME,
});
}
/**
* Hauptfenster-Seite der Sync-Brücke: hält das Einstellungs-Fenster (falls
* offen) synchron zu `state` und wendet dessen Befehle auf `handlers` an.
* No-op ausserhalb von Tauri.
*/
export function useSettingsWindowHost(
state: SettingsSyncState,
handlers: SettingsCommandHandlers,
): void {
const handlersRef = useRef(handlers);
handlersRef.current = handlers;
const stateRef = useRef(state);
stateRef.current = state;
useEffect(() => {
if (!isTauriRuntime()) return;
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<{ name: string; args: unknown[] }>("settings-command", (e) => {
const fn = (handlersRef.current as unknown as Record<string, unknown>)[e.payload.name];
if (typeof fn === "function") (fn as (...a: unknown[]) => void)(...e.payload.args);
}),
);
unlisten.push(
await listen("settings-window-ready", () => {
void emitTo(SETTINGS_WINDOW_LABEL, "settings-sync-state", stateRef.current).catch(
() => {},
);
}),
);
})();
return () => {
disposed = true;
unlisten.forEach((u) => u());
};
}, []);
useEffect(() => {
if (!isTauriRuntime()) return;
void (async () => {
const { emitTo } = await import("@tauri-apps/api/event");
await emitTo(SETTINGS_WINDOW_LABEL, "settings-sync-state", state).catch(() => {});
})();
}, [state]);
}