Start-Sequenz ergänzt: Splash-Screen, dann Startbildschirm mit Neues Projekt/Projekt öffnen/Zuletzt geöffnet.
Zuletzt-geöffnet-Liste (state/recentProjects.ts) trägt sich beim Öffnen und beim Speichern automatisch ein, inkl. Öffnen über einen bekannten Pfad ohne Dialog (verwaiste Einträge fliegen raus, wenn die Datei fehlt). Der Splash wartet bewusst nicht auf den Update-Check — der läuft unabhängig im Hintergrund weiter, damit ein langsames Netz den Start nie verzögert.
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
// laufen. Die Slice tippt daher nur ihre eigenen Felder; `get()` liefert
|
||||
// RootState (siehe appStore.ts), wir greifen punktuell auf View-Felder zu.
|
||||
|
||||
import { emptyProject } from "../model/sampleProject";
|
||||
import { sampleProject } from "../model/sampleProject";
|
||||
import {
|
||||
flattenCategories,
|
||||
recomputeFloorElevations,
|
||||
@@ -363,10 +363,11 @@ export function createProjectSlice(
|
||||
return {
|
||||
...history,
|
||||
|
||||
// TEMPORÄR für manuellen Test (Nutzer-Wunsch): leeres Projekt statt Demo-
|
||||
// Haus. NICHT committen — vor dem nächsten echten Commit zurück auf
|
||||
// `sampleProject` stellen.
|
||||
project: emptyProject,
|
||||
// Store-Default beim Modul-Laden — sichtbar nur ganz kurz HINTER dem
|
||||
// Splash/Startbildschirm (App.tsx bootPhase: splash → start → editor),
|
||||
// bevor eine der drei Optionen (Neu/Öffnen/Zuletzt) das echte Projekt
|
||||
// setzt. Der konkrete Wert spielt daher praktisch keine Rolle mehr.
|
||||
project: sampleProject,
|
||||
|
||||
setProject,
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Liste zuletzt geöffneter/gespeicherter Projektdateien für den Startbildschirm
|
||||
// (s. ui/StartScreen.tsx). Nur Pfade + Metadaten, lokal in localStorage — der
|
||||
// Dateiinhalt selbst wird erst beim Anklicken über den echten Pfad geladen
|
||||
// (io/projectFile.ts), damit die Liste nie mit dem Dateiinhalt auseinanderläuft.
|
||||
// Nur unter Tauri sinnvoll (echte Dateipfade); im Browser bleibt sie leer.
|
||||
|
||||
const STORAGE_KEY = "dossier.recentProjects";
|
||||
const MAX_ENTRIES = 8;
|
||||
|
||||
export interface RecentProjectEntry {
|
||||
path: string;
|
||||
name: string;
|
||||
/** ISO-Zeitstempel des letzten Öffnens/Speicherns. */
|
||||
openedAt: string;
|
||||
}
|
||||
|
||||
function readAll(): RecentProjectEntry[] {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return [];
|
||||
const parsed = JSON.parse(raw);
|
||||
return Array.isArray(parsed) ? (parsed as RecentProjectEntry[]) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function writeAll(entries: RecentProjectEntry[]): void {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));
|
||||
}
|
||||
|
||||
/** Neueste zuerst. */
|
||||
export function getRecentProjects(): RecentProjectEntry[] {
|
||||
return readAll();
|
||||
}
|
||||
|
||||
/** Trägt einen Pfad als "gerade geöffnet/gespeichert" ein (an den Anfang,
|
||||
* Duplikate entfernt, auf MAX_ENTRIES gekappt). */
|
||||
export function addRecentProject(path: string, name: string): void {
|
||||
const rest = readAll().filter((e) => e.path !== path);
|
||||
const next = [{ path, name, openedAt: new Date().toISOString() }, ...rest].slice(
|
||||
0,
|
||||
MAX_ENTRIES,
|
||||
);
|
||||
writeAll(next);
|
||||
}
|
||||
|
||||
/** Entfernt einen Eintrag (z. B. wenn die Datei beim Öffnen nicht mehr existiert). */
|
||||
export function removeRecentProject(path: string): void {
|
||||
writeAll(readAll().filter((e) => e.path !== path));
|
||||
}
|
||||
@@ -21,12 +21,14 @@ import { saveTextFile } from "../io/saveFile";
|
||||
import {
|
||||
saveProjectFile,
|
||||
openProjectFileNative,
|
||||
openProjectFileAtPath,
|
||||
acquireProjectLock,
|
||||
releaseProjectLock,
|
||||
registerLockAutoRelease,
|
||||
isTauriRuntime,
|
||||
} from "../io/projectFile";
|
||||
import type { LockInfo } from "../io/projectFile";
|
||||
import { addRecentProject, removeRecentProject } from "./recentProjects";
|
||||
import type { ContextObject, Drawing2D, Project, Vec2 } from "../model/types";
|
||||
import type { PlanViewHandle } from "../plan/PlanView";
|
||||
|
||||
@@ -187,7 +189,9 @@ export function useProjectFileActions(
|
||||
// erlaubt beide Endungen).
|
||||
const onSaveProject = () => {
|
||||
const json = JSON.stringify(project, null, 2);
|
||||
void saveProjectFile(json, project.name);
|
||||
void saveProjectFile(json, project.name, (path) =>
|
||||
addRecentProject(path, project.name || "modell"),
|
||||
);
|
||||
};
|
||||
// Gibt den Lock der zuletzt geöffneten Projektdatei frei (falls wir ihn
|
||||
// halten) und vergisst den Pfad — vor dem Wechsel auf ein anderes Projekt.
|
||||
@@ -212,6 +216,7 @@ export function useProjectFileActions(
|
||||
setProject(parsed as Project);
|
||||
openedProjectPathRef.current = path;
|
||||
projectLockOwnedRef.current = lockOwned;
|
||||
addRecentProject(path, (parsed as Project).name || "modell");
|
||||
} catch (err) {
|
||||
console.error("Projekt-Import fehlgeschlagen:", err);
|
||||
}
|
||||
@@ -238,6 +243,24 @@ export function useProjectFileActions(
|
||||
setLockConflict({ path: opened.path, content: opened.content, info: lock.info });
|
||||
}
|
||||
};
|
||||
// Öffnen über einen BEKANNTEN Pfad (Startbildschirm/„Zuletzt geöffnet",
|
||||
// s. ui/StartScreen.tsx) — kein Dialog, sonst identischer Lock-Fluss wie
|
||||
// `openProjectViaNativeDialog`. Ist die Datei nicht mehr lesbar (gelöscht/
|
||||
// verschoben), wird der verwaiste Eintrag aus der Liste entfernt.
|
||||
const openProjectAtPath = async (path: string): Promise<boolean> => {
|
||||
const opened = await openProjectFileAtPath(path);
|
||||
if (!opened) {
|
||||
removeRecentProject(path);
|
||||
return false;
|
||||
}
|
||||
const lock = await acquireProjectLock(opened.path);
|
||||
if (lock.ok) {
|
||||
applyOpenedProjectFile(opened.path, opened.content, true);
|
||||
} else {
|
||||
setLockConflict({ path: opened.path, content: opened.content, info: lock.info });
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// Datei speichern: unter Tauri nativer „Speichern unter"-Dialog (Ordner + Name),
|
||||
// im Browser Blob-Download-Fallback — die gesamte Logik (inkl. WKWebView-
|
||||
// Härtung: octet-stream-MIME + verzögertes revoke) steckt in saveTextFile.
|
||||
@@ -351,6 +374,7 @@ export function useProjectFileActions(
|
||||
releaseCurrentProjectLock,
|
||||
applyOpenedProjectFile,
|
||||
openProjectViaNativeDialog,
|
||||
openProjectAtPath,
|
||||
downloadTextFile,
|
||||
projectFileName,
|
||||
runExport,
|
||||
|
||||
Reference in New Issue
Block a user