Files
DOSSIER-STANDALONE/src/io/saveFile.ts
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

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;
}