Files
DOSSIER-STANDALONE/src/ui/ExportSaveDialog.tsx
T
karim 35299307d6 Akkumulierten grünen Arbeitsstand landen (Basis für Weiterarbeit)
Bündelt den über mehrere Sessions gewachsenen, uncommitteten Stand in
einem Basis-Commit, damit Folge-Features isoliert darauf aufsetzen.
Verifikation: tsc --noEmit sauber, vitest 600/600 grün.

Enthalten (Details in PENDENZEN.md ✅-Liste / HANDOVER.md):
- truck-Integration: Profil-Extrusion + Verjüngung + Boolean-CSG (csgrs),
  Crate src-tauri/trucksolid, Werkzeug `extrude`, ExtrudedSolid-Modell.
- kernel2d-Port nach Rust/WASM (Phasen 1–5, Diff-Harness).
- render3d 3D-Live-Schnitt = 2D-Schnitt: geschichteter Bodenaufbau,
  Prioritäts-Verschneidung (section_boolean.rs), einstellbare
  Schichttrennlinien, per-Hatch-Strichstärke, relativeToWall-Orientierung.
- Interop-Export IFC4/STL/OBJ (Loch-Ausschnitt wallMeshCut), Schnellexport.
- Projektdatei .obp + OS-Lock (lock.rs, LockConflictDialog).
- Layout-Blätter (Modell/Editor/Panel/PDF), Ausschnitte, Override-Engine,
  Tragwerk-Stützen (Column), BIM-Tree-Panel.
- Bauteil-Typsystem (Tür/Fenster/Treppe-Typen), Betontreppe mit schräger
  Laufplatte, Text-/Textbox-Werkzeug, Mess-Werkzeug, 2D/3D-Griffe für
  Öffnungen/Treppen, Snap-Symbol-Restyle.
2026-07-09 00:57:29 +02:00

126 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Schnellexport-Dialog (modal) für die Direkt-Formate CSV/IFC/OBJ/STL aus dem
// Topbar-Export-Menü: Format wählen + Dateiname festlegen, dann speichern.
// (PDF/DXF haben eigene Options-Dialoge.) Später bekommen „Ausschnitte" einen
// vordefinierten Namen + Speicherort; dies ist der interaktive Schnellweg.
//
// Muster wie ExportDxfDialog: abgedunkeltes Overlay, Klick auf Hintergrund + Esc
// schliessen. Rein gesteuert — meldet Format + Dateiname über `onSave` nach oben
// (App führt Erzeugung + Download aus). Bezeichner englisch, UI deutsch (t()).
import { useEffect, useState } from "react";
import { t } from "../i18n";
import { Dropdown } from "./Dropdown";
/** Direkt-herunterladbare Exportformate (mit Datei-Endung). */
export type ExportSaveFormat = "csv" | "ifc" | "obj" | "stl";
/** Datei-Endung je Format. */
export const EXPORT_FORMAT_EXT: Record<ExportSaveFormat, string> = {
csv: "csv",
ifc: "ifc",
obj: "obj",
stl: "stl",
};
export interface ExportSaveDialogProps {
/** Vorausgewähltes Format (aus dem angeklickten Menüeintrag). */
initialFormat: ExportSaveFormat;
/** Basis-Dateiname OHNE Endung (i. d. R. Projektname). */
baseName: string;
onSave: (format: ExportSaveFormat, filename: string) => void;
onClose: () => void;
}
/** Endung sicher an den Basisnamen hängen (doppelte Endung vermeiden). */
function withExt(base: string, ext: string): string {
const trimmed = base.trim() || "modell";
const lower = trimmed.toLowerCase();
return lower.endsWith(`.${ext}`) ? trimmed : `${trimmed}.${ext}`;
}
export function ExportSaveDialog({
initialFormat,
baseName,
onSave,
onClose,
}: ExportSaveDialogProps) {
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [onClose]);
const [format, setFormat] = useState<ExportSaveFormat>(initialFormat);
// Der Dateiname wird OHNE Endung editiert; die Endung folgt aus dem Format.
const [name, setName] = useState(baseName);
const filename = withExt(name, EXPORT_FORMAT_EXT[format]);
const onConfirm = () => onSave(format, filename);
return (
<div className="imp-overlay" onClick={onClose}>
<div
className="imp-dialog"
role="dialog"
aria-modal="true"
aria-label={t("exportSave.title")}
onClick={(e) => e.stopPropagation()}
>
<header className="imp-head">
<span className="imp-head-title">{t("exportSave.title")}</span>
<button className="imp-close" onClick={onClose} aria-label={t("export.close")}>
×
</button>
</header>
<div className="imp-body">
<section className="imp-section">
<div className="imp-section-title">{t("exportSave.format")}</div>
<Dropdown
value={format}
onChange={(v) => setFormat(v as ExportSaveFormat)}
title={t("exportSave.format")}
options={[
{ value: "csv", label: t("exportSave.fmt.csv") },
{ value: "ifc", label: t("exportSave.fmt.ifc") },
{ value: "obj", label: t("exportSave.fmt.obj") },
{ value: "stl", label: t("exportSave.fmt.stl") },
]}
/>
</section>
<section className="imp-section">
<div className="imp-section-title">{t("exportSave.filename")}</div>
<input
className="settings-num-input"
type="text"
value={name}
autoFocus
spellCheck={false}
onChange={(e) => setName(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") onConfirm();
}}
placeholder="modell"
/>
<div className="imp-note">
{filename} · {t("exportSave.locationNote")}
</div>
</section>
</div>
<footer className="imp-foot">
<button className="imp-btn" onClick={onClose}>
{t("export.cancel")}
</button>
<button className="imp-btn primary" onClick={onConfirm}>
{t("export.confirm")}
</button>
</footer>
</div>
</div>
);
}