- Page transitions with slide-up/fade on route changes (App.vue) - NavRail sliding active indicator with spring-like easing - List enter/leave/move animations on Entries, Projects, Clients, Timer - Modal enter/leave transitions with scale+fade on all dialogs - Dropdown transitions with overshoot on all select/picker components - Button feedback (scale on hover/active), card hover lift effects - Timer pulse on start, glow on stop, floating empty state icons - Content fade-in on Dashboard, Reports, Calendar, Timesheet - Tag chip enter/leave animations in AppTagInput - Progress bar smooth width transitions - Implementation plan document
92 lines
2.8 KiB
Vue
92 lines
2.8 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, watch } from 'vue'
|
|
import TitleBar from './components/TitleBar.vue'
|
|
import NavRail from './components/NavRail.vue'
|
|
import ToastNotification from './components/ToastNotification.vue'
|
|
import { useSettingsStore } from './stores/settings'
|
|
import { useTimerStore } from './stores/timer'
|
|
|
|
const settingsStore = useSettingsStore()
|
|
|
|
async function registerShortcuts() {
|
|
try {
|
|
const { unregisterAll, register } = await import('@tauri-apps/plugin-global-shortcut')
|
|
await unregisterAll()
|
|
const toggleKey = settingsStore.settings.shortcut_toggle_timer || 'CmdOrCtrl+Shift+T'
|
|
const showKey = settingsStore.settings.shortcut_show_app || 'CmdOrCtrl+Shift+Z'
|
|
|
|
await register(toggleKey, () => {
|
|
const timerStore = useTimerStore()
|
|
if (timerStore.isStopped) {
|
|
if (timerStore.selectedProjectId) timerStore.start()
|
|
} else {
|
|
timerStore.stop()
|
|
}
|
|
})
|
|
|
|
await register(showKey, async () => {
|
|
const { getCurrentWindow } = await import('@tauri-apps/api/window')
|
|
const win = getCurrentWindow()
|
|
await win.show()
|
|
await win.setFocus()
|
|
})
|
|
} catch (e) {
|
|
console.error('Failed to register shortcuts:', e)
|
|
}
|
|
}
|
|
|
|
function applyTheme() {
|
|
const el = document.documentElement
|
|
const mode = settingsStore.settings.theme_mode || 'dark'
|
|
const accent = settingsStore.settings.accent_color || 'amber'
|
|
|
|
if (mode === 'system') {
|
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
el.setAttribute('data-theme', prefersDark ? 'dark' : 'light')
|
|
} else {
|
|
el.setAttribute('data-theme', mode)
|
|
}
|
|
el.setAttribute('data-accent', accent)
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await settingsStore.fetchSettings()
|
|
const zoom = parseInt(settingsStore.settings.ui_zoom) || 100
|
|
const app = document.getElementById('app')
|
|
if (app) {
|
|
(app.style as any).zoom = `${zoom}%`
|
|
}
|
|
applyTheme()
|
|
registerShortcuts()
|
|
|
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
|
if (settingsStore.settings.theme_mode === 'system') applyTheme()
|
|
})
|
|
})
|
|
|
|
watch(() => [settingsStore.settings.theme_mode, settingsStore.settings.accent_color], () => {
|
|
applyTheme()
|
|
})
|
|
|
|
watch(() => [settingsStore.settings.shortcut_toggle_timer, settingsStore.settings.shortcut_show_app], () => {
|
|
registerShortcuts()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="h-full w-full flex flex-col bg-bg-base">
|
|
<TitleBar />
|
|
<div class="flex-1 flex overflow-hidden">
|
|
<NavRail />
|
|
<main class="flex-1 overflow-auto">
|
|
<router-view v-slot="{ Component }">
|
|
<Transition name="page" mode="out-in">
|
|
<component :is="Component" :key="$route.path" />
|
|
</Transition>
|
|
</router-view>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
<ToastNotification />
|
|
</template>
|