35299307d6
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.
117 lines
4.0 KiB
TypeScript
117 lines
4.0 KiB
TypeScript
// Datei-Speicherung mit nativem „Speichern unter"-Dialog im Tauri-Fenster,
|
|
// im reinen Browser Fallback auf den klassischen Blob-/Anchor-Download.
|
|
//
|
|
// Wichtig: Die Tauri-Plugins werden NUR dynamisch importiert. So scheitert der
|
|
// reine Browser-Build/vitest nicht an fehlenden Tauri-Modulen und der Code laeuft
|
|
// auch ohne Tauri-Laufzeit.
|
|
|
|
/** Erkennt, ob wir im Tauri-Webview laufen (dann steht `__TAURI_INTERNALS__`). */
|
|
function isTauri(): boolean {
|
|
return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
|
|
}
|
|
|
|
let warnedFallback = false;
|
|
|
|
/** Datei-Endungsfilter fuer den nativen Speichern-/Oeffnen-Dialog (Tauri). */
|
|
export interface FileDialogFilter {
|
|
name: string;
|
|
extensions: string[];
|
|
}
|
|
|
|
/**
|
|
* Speichert Text-Inhalt in eine Datei.
|
|
* - Unter Tauri: nativer Save-Dialog (Ordner + Name) → `writeTextFile`.
|
|
* - Im Browser: Blob + Anchor-Download in den Download-Ordner.
|
|
*
|
|
* @param filters Optionale Datei-Endungsfilter fuer den nativen Dialog (z. B.
|
|
* Projekt-Endung). Additiv/optional, damit bestehende Aufrufer ohne
|
|
* Filter unveraendert funktionieren (Default-Verhalten: irgendeine Endung).
|
|
* @returns `true` wenn geschrieben/geladen wurde, `false` bei Abbruch (Dialog
|
|
* ohne Pfad). Bei einem Tauri-Fehler wird auf den Browser-Fallback
|
|
* zurueckgefallen.
|
|
*/
|
|
export async function saveTextFile(
|
|
content: string,
|
|
defaultName: string,
|
|
mime = "application/octet-stream",
|
|
filters?: FileDialogFilter[],
|
|
): Promise<boolean> {
|
|
if (isTauri()) {
|
|
try {
|
|
const { save } = await import("@tauri-apps/plugin-dialog");
|
|
const path = await save({ defaultPath: defaultName, filters });
|
|
if (!path) return false; // Nutzer hat abgebrochen
|
|
const { writeTextFile } = await import("@tauri-apps/plugin-fs");
|
|
await writeTextFile(path, content);
|
|
return true;
|
|
} catch (err) {
|
|
if (!warnedFallback) {
|
|
warnedFallback = true;
|
|
console.warn(
|
|
"Nativer Speichern-Dialog fehlgeschlagen, nutze Browser-Download.",
|
|
err,
|
|
);
|
|
}
|
|
// Weiter zum Browser-Fallback unten.
|
|
}
|
|
}
|
|
return browserDownload(content, defaultName, mime);
|
|
}
|
|
|
|
/**
|
|
* Speichert Binaer-Inhalt (z. B. ein PDF) in eine Datei — gleiches Tauri-/
|
|
* Browser-Muster wie {@link saveTextFile}, nur mit `writeFile` (Bytes) statt
|
|
* `writeTextFile`.
|
|
* - Unter Tauri: nativer Save-Dialog → `writeFile(path, bytes)`.
|
|
* - Im Browser: Blob + Anchor-Download.
|
|
*
|
|
* @returns `true` wenn geschrieben/geladen wurde, `false` bei Abbruch.
|
|
*/
|
|
export async function saveBinaryFile(
|
|
bytes: Uint8Array,
|
|
defaultName: string,
|
|
mime = "application/octet-stream",
|
|
): Promise<boolean> {
|
|
if (isTauri()) {
|
|
try {
|
|
const { save } = await import("@tauri-apps/plugin-dialog");
|
|
const path = await save({ defaultPath: defaultName });
|
|
if (!path) return false; // Nutzer hat abgebrochen
|
|
const { writeFile } = await import("@tauri-apps/plugin-fs");
|
|
await writeFile(path, bytes);
|
|
return true;
|
|
} catch (err) {
|
|
if (!warnedFallback) {
|
|
warnedFallback = true;
|
|
console.warn(
|
|
"Nativer Speichern-Dialog fehlgeschlagen, nutze Browser-Download.",
|
|
err,
|
|
);
|
|
}
|
|
// Weiter zum Browser-Fallback unten.
|
|
}
|
|
}
|
|
return browserDownloadBlob(new Blob([bytes as BlobPart], { type: mime }), defaultName);
|
|
}
|
|
|
|
/** Klassischer Blob-/Anchor-Download (gehaertet wie der bisherige Helfer). */
|
|
function browserDownload(content: string, defaultName: string, mime: string): boolean {
|
|
return browserDownloadBlob(new Blob([content], { type: mime }), defaultName);
|
|
}
|
|
|
|
/** Anchor-Download eines fertigen Blobs (Text wie Binaer). */
|
|
function browserDownloadBlob(blob: Blob, defaultName: string): boolean {
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = defaultName;
|
|
a.rel = "noopener";
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
setTimeout(() => {
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
}, 4000);
|
|
return true;
|
|
}
|