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.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user