mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Polish the migration
This commit is contained in:
@@ -1,9 +1,53 @@
|
||||
<script lang="ts">
|
||||
import pkg from '../../../../package.json'
|
||||
import { appState } from '$lib/state/app.svelte'
|
||||
import { settingsState } from '$lib/state/settings.svelte'
|
||||
import { trackingState } from '$lib/state/tracking.svelte'
|
||||
import { checkForAppUpdate, installAppUpdate, isSupported } from '$lib/platform-service'
|
||||
import type { AppUpdateInfo } from '$lib/platform-adapters/types'
|
||||
|
||||
const appVersion = pkg.version as string
|
||||
|
||||
let updateInfo = $state<AppUpdateInfo | null>(null)
|
||||
let updateChecking = $state(false)
|
||||
let updateInstalling = $state(false)
|
||||
let updateError = $state<string | null>(null)
|
||||
let updateDone = $state(false)
|
||||
|
||||
const canCheckUpdates = typeof window !== 'undefined' && (() => {
|
||||
try { return isSupported('app-updates') } catch { return false }
|
||||
})()
|
||||
|
||||
async function handleCheckUpdate() {
|
||||
updateChecking = true
|
||||
updateError = null
|
||||
updateInfo = null
|
||||
updateDone = false
|
||||
|
||||
try {
|
||||
updateInfo = await checkForAppUpdate()
|
||||
} catch (error: unknown) {
|
||||
updateError = error instanceof Error ? error.message : String(error)
|
||||
} finally {
|
||||
updateChecking = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleInstallUpdate() {
|
||||
if (!updateInfo) return
|
||||
|
||||
updateInstalling = true
|
||||
updateError = null
|
||||
|
||||
try {
|
||||
await installAppUpdate()
|
||||
updateDone = true
|
||||
} catch (error: unknown) {
|
||||
updateError = error instanceof Error ? error.message : String(error)
|
||||
} finally {
|
||||
updateInstalling = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -23,8 +67,35 @@
|
||||
<div class="settings-label">Moku</div>
|
||||
<div class="settings-desc">Version {appVersion}</div>
|
||||
</div>
|
||||
{#if canCheckUpdates}
|
||||
<button class="settings-button" type="button" onclick={handleCheckUpdate} disabled={updateChecking}>
|
||||
{updateChecking ? 'Checking…' : 'Check for updates'}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if updateInfo}
|
||||
<div class="settings-row">
|
||||
<div>
|
||||
<div class="settings-label">Update available</div>
|
||||
<div class="settings-desc">v{updateInfo.version}</div>
|
||||
</div>
|
||||
<button class="settings-button" type="button" onclick={handleInstallUpdate} disabled={updateInstalling}>
|
||||
{updateInstalling ? 'Installing…' : 'Install now'}
|
||||
</button>
|
||||
</div>
|
||||
{:else if updateChecking === false && updateError === null && updateInfo === null && updateDone === false && canCheckUpdates}
|
||||
<!-- idle, no explicit "up to date" message unless user just clicked -->
|
||||
{/if}
|
||||
|
||||
{#if updateDone}
|
||||
<p class="settings-feedback-ok">Update installed — please restart Moku.</p>
|
||||
{/if}
|
||||
|
||||
{#if updateError}
|
||||
<p class="settings-feedback-error">{updateError}</p>
|
||||
{/if}
|
||||
|
||||
<div class="settings-row settings-grid-2">
|
||||
<div>
|
||||
<div class="settings-label">Server URL</div>
|
||||
|
||||
@@ -1,5 +1,100 @@
|
||||
<script lang="ts">
|
||||
import { appState } from '$lib/state/app.svelte'
|
||||
import { settingsState, updateSettings } from '$lib/state/settings.svelte'
|
||||
import { historyState } from '$lib/state/history.svelte'
|
||||
import {
|
||||
buildAppDataBackup,
|
||||
downloadAppDataBackup,
|
||||
parseAppDataBackup,
|
||||
pickAppDataBackupFile,
|
||||
} from '$lib/core/backup'
|
||||
import { isSupported } from '$lib/platform-service'
|
||||
import { savePersistentState } from '$lib/core/persistence/persist'
|
||||
|
||||
let exportBusy = $state(false)
|
||||
let importBusy = $state(false)
|
||||
let backupError = $state<string | null>(null)
|
||||
let backupMsg = $state<string | null>(null)
|
||||
|
||||
const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window
|
||||
|
||||
async function handleExport() {
|
||||
exportBusy = true
|
||||
backupError = null
|
||||
backupMsg = null
|
||||
|
||||
try {
|
||||
if (isTauri && isSupported('filesystem')) {
|
||||
// Tauri-native export via invoke not yet wired — fall through to web path
|
||||
const backup = buildAppDataBackup(settingsState, {
|
||||
history: historyState.history,
|
||||
bookmarks: historyState.bookmarks,
|
||||
markers: historyState.markers,
|
||||
readLog: historyState.readLog,
|
||||
readingStats: historyState.readingStats as unknown as Record<string, unknown>,
|
||||
dailyReadCounts: historyState.dailyReadCounts,
|
||||
})
|
||||
downloadAppDataBackup(backup)
|
||||
backupMsg = 'Backup downloaded.'
|
||||
} else {
|
||||
const backup = buildAppDataBackup(settingsState, {
|
||||
history: historyState.history,
|
||||
bookmarks: historyState.bookmarks,
|
||||
markers: historyState.markers,
|
||||
readLog: historyState.readLog,
|
||||
readingStats: historyState.readingStats as unknown as Record<string, unknown>,
|
||||
dailyReadCounts: historyState.dailyReadCounts,
|
||||
})
|
||||
downloadAppDataBackup(backup)
|
||||
backupMsg = 'Backup downloaded.'
|
||||
}
|
||||
|
||||
setTimeout(() => (backupMsg = null), 3000)
|
||||
} catch (error: unknown) {
|
||||
if (String(error).includes('Cancelled') || String(error).includes('AbortError')) {
|
||||
// user cancelled
|
||||
} else {
|
||||
backupError = error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
} finally {
|
||||
exportBusy = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleImport() {
|
||||
importBusy = true
|
||||
backupError = null
|
||||
backupMsg = null
|
||||
|
||||
try {
|
||||
// Tauri-native import handled below — same web path works
|
||||
|
||||
const file = await pickAppDataBackupFile()
|
||||
if (!file) return
|
||||
|
||||
const text = await file.text()
|
||||
const backup = parseAppDataBackup(text)
|
||||
|
||||
await Promise.all([
|
||||
savePersistentState('settings', {
|
||||
settings: backup.settings,
|
||||
storeVersion: 1,
|
||||
}),
|
||||
savePersistentState('history', backup.history),
|
||||
])
|
||||
|
||||
backupMsg = 'Import complete — reloading in 3 seconds…'
|
||||
setTimeout(() => window.location.reload(), 3000)
|
||||
} catch (error: unknown) {
|
||||
if (String(error).includes('Cancelled') || String(error).includes('AbortError')) {
|
||||
// user cancelled
|
||||
} else {
|
||||
backupError = error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
} finally {
|
||||
importBusy = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -34,4 +129,29 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<div class="settings-row">
|
||||
<div>
|
||||
<div class="settings-label">App data backup</div>
|
||||
<div class="settings-desc">Export or import Moku settings and reading history.</div>
|
||||
</div>
|
||||
<div class="settings-inline-control">
|
||||
<button class="settings-button" type="button" onclick={handleExport} disabled={exportBusy}>
|
||||
{exportBusy ? 'Exporting…' : 'Export backup'}
|
||||
</button>
|
||||
<button class="settings-button" type="button" onclick={handleImport} disabled={importBusy}>
|
||||
{importBusy ? 'Importing…' : 'Import backup'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if backupMsg}
|
||||
<p class="settings-feedback-ok">{backupMsg}</p>
|
||||
{/if}
|
||||
|
||||
{#if backupError}
|
||||
<p class="settings-feedback-error">{backupError}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
Reference in New Issue
Block a user