feat(04-03): implement Export/Import/Reset data logic in parent area

- exportData: downloads progress+settings as zauberwald-backup.json
- importData: validates JSON structure, confirms overwrite, saves and reloads
- resetAllData: double confirmation, clears all 5 IndexedDB stores, reloads
- Buttons wired in loadParentData after code entry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 18:16:51 +02:00
co-authored by Claude Opus 4.6
parent f3cb948878
commit 7931f7ec39
+114
View File
@@ -85,6 +85,16 @@ async function loadParentData(): Promise<void> {
// Settings
await loadSettings(db, progress, settings);
// Wire data action buttons
document.getElementById("export-btn")!.onclick = () => exportData(db);
document.getElementById("import-btn")!.onclick = () =>
(document.getElementById("import-file") as HTMLInputElement).click();
document.getElementById("import-file")!.addEventListener("change", (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) importData(db, file);
});
document.getElementById("reset-btn")!.onclick = () => resetAllData(db);
}
/**
@@ -186,6 +196,7 @@ async function loadSettings(
const currentSettings = settings ?? {
id: 1 as const,
audioEnabled: false,
speechEnabled: false,
apiKey: "",
apiCallsToday: { text: 0, image: 0 },
lastApiCallDate: "",
@@ -199,6 +210,7 @@ async function loadSettings(
const currentSettings = settings ?? {
id: 1 as const,
audioEnabled: false,
speechEnabled: false,
apiKey: "",
apiCallsToday: { text: 0, image: 0 },
lastApiCallDate: "",
@@ -209,6 +221,108 @@ async function loadSettings(
};
}
const DB_STORES = ["progress", "companionAssets", "styleReference", "forestElements", "settings"];
/**
* Show feedback message in the data section.
*/
function showDataFeedback(message: string, isError = false): void {
const el = document.getElementById("data-feedback")!;
el.textContent = message;
el.style.display = "block";
el.style.color = isError ? "#D46A6A" : "";
setTimeout(() => {
el.style.display = "none";
}, 3000);
}
/**
* Export progress and settings as a JSON file download.
*/
async function exportData(db: IDBDatabase): Promise<void> {
const progress = await getProgress(db);
const settings = await getSettings(db);
const data = {
version: 1,
exportedAt: new Date().toISOString(),
progress,
settings,
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "zauberwald-backup.json";
a.click();
URL.revokeObjectURL(url);
showDataFeedback("Exportiert!");
}
/**
* Import progress and settings from a JSON file.
*/
async function importData(db: IDBDatabase, file: File): Promise<void> {
try {
const text = await file.text();
const parsed = JSON.parse(text);
// Validate structure
if (
!parsed.progress ||
typeof parsed.progress.currentLevel !== "number" ||
!Array.isArray(parsed.progress.completedLevels) ||
typeof parsed.progress.totalSessions !== "number" ||
typeof parsed.progress.selectedCharacter !== "string" ||
typeof parsed.progress.selectedLayout !== "string"
) {
showDataFeedback("Ungueltige Datei", true);
return;
}
if (!parsed.settings || typeof parsed.settings.audioEnabled !== "boolean") {
showDataFeedback("Ungueltige Datei", true);
return;
}
if (!confirm("Fortschritt ueberschreiben? Aktuelle Daten gehen verloren.")) {
return;
}
await saveProgress(db, { ...parsed.progress, id: 1 });
await saveSettings(db, { ...parsed.settings, id: 1 });
showDataFeedback("Importiert!");
setTimeout(() => location.reload(), 1500);
} catch {
showDataFeedback("Ungueltige Datei", true);
}
}
/**
* Clear a single IndexedDB object store.
*/
function clearStore(db: IDBDatabase, storeName: string): Promise<void> {
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, "readwrite");
const request = tx.objectStore(storeName).clear();
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
/**
* Reset all data with double confirmation, then reload.
*/
async function resetAllData(db: IDBDatabase): Promise<void> {
if (!confirm("Wirklich alles loeschen?")) return;
if (!confirm("Alle Fortschritte und Einstellungen werden geloescht. Bist du sicher?")) return;
await Promise.all(DB_STORES.map((store) => clearStore(db, store)));
location.reload();
}
/**
* Show brief "Gespeichert!" feedback next to an element.
*/