Files
DOSSIER-STANDALONE/src/native/layerSettingsWindow.ts
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

135 lines
5.2 KiB
TypeScript

// Ebeneneinstellungen als EIGENES natives Tauri-Fenster — Referenz: DOSSIER-
// Rhino-Plugin (`git.kgva.ch/karim/DOSSIER`, `rhino/layers_panel.py`
// `_open_ebenen_settings`), das dort exakt so als „Satelliten-Fenster"
// (Eto-Form, kein Docked-Panel) läuft. Ersetzt unter Tauri den bisherigen
// `InlineEditor`-Popover (App.tsx, `editor?.kind === "layer"`); im reinen
// Browser bleibt der Popover (kein OS-Fenster-Konzept dort).
//
// **Bewusst NUR die heute schon im Modell vorhandenen Felder** (Name/Farbe/
// Linienstärke/Schraffur-Auswahl) — die Rhino-Version kennt zusätzlich eine
// PRO-EBENE Schraffur-Skalierung/-Rotation/-Stiftstärke (`section.hatchScale`
// etc.), die es in `LayerCategory` (model/types.ts) schlicht nicht gibt
// (dort nur `hatch?: string`, eine Referenz auf die geteilte Hatch-Resource
// ohne Per-Kategorie-Override). Das nachzurüsten wäre eine eigene
// Modell-Erweiterung + generatePlan.ts-Verdrahtung, keine reine Fenster-
// Frage — bewusst nicht mitgemacht, um hier nichts zu erfinden, das der Rest
// der App nicht einlöst.
//
// Nur EIN Fenster gleichzeitig (Label fix "layer-settings", wie Rhinos
// Satellit): erneutes Öffnen für eine andere Ebene schiebt nur den neuen Code
// nach (kein Fenster-Zoo), analog dem Dropdown-Wechsel im Original.
//
// Bezeichner englisch, Kommentare deutsch (CONVENTIONS.md).
import { useEffect, useRef } from "react";
import { isTauriRuntime } from "../io/projectFile";
import { findCategory } from "../state/projectSlice";
import type { HatchStyle, LayerCategory, Project } from "../model/types";
import { BORDERLESS_WINDOW_CHROME } from "./windowChrome";
const LAYER_SETTINGS_WINDOW_LABEL = "layer-settings";
export interface LayerSettingsSyncState {
code: string;
name: string;
color: string;
lw: number;
hatchId: string | undefined;
hatches: { id: string; name: string }[];
}
export interface LayerSettingsCommandHandlers {
onPatchCategory: (code: string, patch: Partial<LayerCategory>) => void;
}
/** Öffnet das native Ebeneneinstellungs-Fenster für `code` (bzw. schiebt den Code nach, falls schon offen). */
export async function openLayerSettingsWindowNative(code: string): Promise<void> {
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
const existing = await WebviewWindow.getByLabel(LAYER_SETTINGS_WINDOW_LABEL);
if (existing) {
const { emitTo } = await import("@tauri-apps/api/event");
await emitTo(LAYER_SETTINGS_WINDOW_LABEL, "layer-settings-set-code", code).catch(() => {});
await existing.setFocus();
return;
}
new WebviewWindow(LAYER_SETTINGS_WINDOW_LABEL, {
url: `index.html?window=layer-settings&code=${encodeURIComponent(code)}`,
title: "Ebeneneinstellungen — Dossier",
width: 360,
height: 420,
minWidth: 300,
minHeight: 340,
...BORDERLESS_WINDOW_CHROME,
});
}
/** Baut den Sync-Zustand für eine Kategorie (bzw. `null`, wenn der Code nicht mehr existiert — z. B. gelöscht). */
function stateFor(project: Project, code: string): LayerSettingsSyncState | null {
const cat = findCategory(project.layers, code);
if (!cat) return null;
return {
code: cat.code,
name: cat.name,
color: cat.color,
lw: cat.lw,
hatchId: cat.hatch,
hatches: project.hatches.map((h: HatchStyle) => ({ id: h.id, name: h.name })),
};
}
/** Hauptfenster-Seite der Sync-Brücke. `code` = aktuell im Fenster editierte Ebene (folgt `openLayerSettingsWindowNative`-Aufrufen). */
export function useLayerSettingsWindowHost(
project: Project,
code: string | null,
onPatchCategory: (code: string, patch: Partial<LayerCategory>) => void,
): void {
const stateRef = useRef<LayerSettingsSyncState | null>(code ? stateFor(project, code) : null);
stateRef.current = code ? stateFor(project, code) : null;
const onPatchRef = useRef(onPatchCategory);
onPatchRef.current = onPatchCategory;
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[] }>("layer-settings-command", (e) => {
if (e.payload.name === "onPatchCategory") {
const [c, patch] = e.payload.args as [string, Partial<LayerCategory>];
onPatchRef.current(c, patch);
}
}),
);
unlisten.push(
await listen("layer-settings-window-ready", () => {
void emitTo(
LAYER_SETTINGS_WINDOW_LABEL,
"layer-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(
LAYER_SETTINGS_WINDOW_LABEL,
"layer-settings-sync-state",
stateRef.current,
).catch(() => {});
})();
// eslint-disable-next-line react-hooks/exhaustive-deps -- stateRef.current ist bereits die aktuelle Ableitung
}, [project, code]);
}