feat: auto-backup UI and window close hook

This commit is contained in:
Your Name
2026-02-20 15:41:38 +02:00
parent 875d3ca23b
commit 5300ceeb12
2 changed files with 93 additions and 0 deletions

View File

@@ -188,6 +188,23 @@ onMounted(async () => {
registerShortcuts() registerShortcuts()
// Auto-backup on window close
try {
const { getCurrentWindow } = await import('@tauri-apps/api/window')
const win = getCurrentWindow()
win.onCloseRequested(async () => {
if (settingsStore.settings.auto_backup === 'true' && settingsStore.settings.backup_path) {
try {
await invoke('auto_backup', { backupDir: settingsStore.settings.backup_path })
} catch (e) {
console.error('Auto-backup failed:', e)
}
}
})
} catch (e) {
console.error('Failed to register close handler:', e)
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
if (settingsStore.settings.theme_mode === 'system') applyTheme() if (settingsStore.settings.theme_mode === 'system') applyTheme()
}) })

View File

@@ -1198,6 +1198,45 @@
</button> </button>
</div> </div>
<!-- Last exported -->
<div v-if="lastExported" class="flex items-center justify-between" role="status">
<div>
<p class="text-[0.8125rem] text-text-primary">Last exported</p>
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">{{ lastExportedFormatted }}</p>
</div>
</div>
<div class="border-t border-border-subtle" />
<!-- Auto-backup on close -->
<div class="flex items-center justify-between">
<div>
<p class="text-[0.8125rem] text-text-primary">Auto-backup on close</p>
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Save a backup when the app closes</p>
</div>
<button @click="toggleAutoBackup" role="switch" :aria-checked="autoBackupEnabled" aria-label="Auto-backup on close"
class="relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
:class="autoBackupEnabled ? 'bg-status-running' : 'bg-bg-elevated'">
<span :class="['inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
autoBackupEnabled ? 'translate-x-[18px]' : 'translate-x-[3px]']" />
</button>
</div>
<!-- Backup path -->
<div v-if="autoBackupEnabled" class="flex items-center justify-between pl-4 border-l-2 border-border-subtle ml-1">
<div class="flex-1 min-w-0">
<p class="text-[0.8125rem] text-text-primary">Backup directory</p>
<p class="text-[0.6875rem] text-text-tertiary mt-0.5 truncate">{{ backupPath || 'Not set' }}</p>
</div>
<button @click="chooseBackupPath"
class="px-3 py-1.5 text-[0.75rem] border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
aria-label="Choose backup directory">
{{ backupPath ? 'Change' : 'Choose' }}
</button>
</div>
<div class="border-t border-border-subtle" />
<!-- Import Data --> <!-- Import Data -->
<div class="mb-8"> <div class="mb-8">
<h3 class="text-[0.8125rem] font-medium text-text-primary mb-4">Import Data</h3> <h3 class="text-[0.8125rem] font-medium text-text-primary mb-4">Import Data</h3>
@@ -1685,6 +1724,34 @@ watch(showClearDataDialog, (val) => {
} }
}) })
// Auto-backup state
const autoBackupEnabled = ref(false)
const backupPath = ref('')
const lastExported = ref('')
const lastExportedFormatted = computed(() => {
if (!lastExported.value) return ''
return new Date(lastExported.value).toLocaleString()
})
async function toggleAutoBackup() {
autoBackupEnabled.value = !autoBackupEnabled.value
await settingsStore.updateSetting('auto_backup', autoBackupEnabled.value ? 'true' : 'false')
}
async function chooseBackupPath() {
try {
const { open } = await import('@tauri-apps/plugin-dialog')
const selected = await open({ directory: true, title: 'Choose backup directory' })
if (selected && typeof selected === 'string') {
backupPath.value = selected
await settingsStore.updateSetting('backup_path', selected)
}
} catch {
// User cancelled
}
}
// Import state // Import state
const importFormat = ref('toggl') const importFormat = ref('toggl')
const importFileContent = ref('') const importFileContent = ref('')
@@ -2103,6 +2170,10 @@ async function exportData() {
a.download = `zeroclock-export-${new Date().toISOString().split('T')[0]}.json` a.download = `zeroclock-export-${new Date().toISOString().split('T')[0]}.json`
a.click() a.click()
URL.revokeObjectURL(url) URL.revokeObjectURL(url)
const now = new Date().toISOString()
await settingsStore.updateSetting('last_exported', now)
lastExported.value = now
} catch (error) { } catch (error) {
console.error('Failed to export data:', error) console.error('Failed to export data:', error)
toastStore.error('Failed to export data') toastStore.error('Failed to export data')
@@ -2158,6 +2229,11 @@ onMounted(async () => {
// Notification settings // Notification settings
persistentNotifications.value = settingsStore.settings.persistent_notifications === 'true' persistentNotifications.value = settingsStore.settings.persistent_notifications === 'true'
// Auto-backup settings
autoBackupEnabled.value = settingsStore.settings.auto_backup === 'true'
backupPath.value = settingsStore.settings.backup_path || ''
lastExported.value = settingsStore.settings.last_exported || ''
// Sound settings // Sound settings
soundEnabled.value = settingsStore.settings.sound_enabled === 'true' soundEnabled.value = settingsStore.settings.sound_enabled === 'true'
soundMode.value = settingsStore.settings.sound_mode || 'synthesized' soundMode.value = settingsStore.settings.sound_mode || 'synthesized'