diff --git a/src/ui/parent.ts b/src/ui/parent.ts index d77a393..95d9132 100644 --- a/src/ui/parent.ts +++ b/src/ui/parent.ts @@ -85,6 +85,16 @@ async function loadParentData(): Promise { // 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 { + 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 { + 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 { + 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 { + 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. */