feat: auto-backup UI and window close hook
This commit is contained in:
17
src/App.vue
17
src/App.vue
@@ -188,6 +188,23 @@ onMounted(async () => {
|
||||
|
||||
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', () => {
|
||||
if (settingsStore.settings.theme_mode === 'system') applyTheme()
|
||||
})
|
||||
|
||||
@@ -1198,6 +1198,45 @@
|
||||
</button>
|
||||
</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 -->
|
||||
<div class="mb-8">
|
||||
<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
|
||||
const importFormat = ref('toggl')
|
||||
const importFileContent = ref('')
|
||||
@@ -2103,6 +2170,10 @@ async function exportData() {
|
||||
a.download = `zeroclock-export-${new Date().toISOString().split('T')[0]}.json`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
const now = new Date().toISOString()
|
||||
await settingsStore.updateSetting('last_exported', now)
|
||||
lastExported.value = now
|
||||
} catch (error) {
|
||||
console.error('Failed to export data:', error)
|
||||
toastStore.error('Failed to export data')
|
||||
@@ -2158,6 +2229,11 @@ onMounted(async () => {
|
||||
// Notification settings
|
||||
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
|
||||
soundEnabled.value = settingsStore.settings.sound_enabled === 'true'
|
||||
soundMode.value = settingsStore.settings.sound_mode || 'synthesized'
|
||||
|
||||
Reference in New Issue
Block a user