|
5 | 5 | <v-icon left>mdi-broom</v-icon> |
6 | 6 | DevNullifier |
7 | 7 | </v-app-bar-title> |
| 8 | + |
| 9 | + <v-spacer></v-spacer> |
| 10 | + |
| 11 | + <v-menu> |
| 12 | + <template v-slot:activator="{ props }"> |
| 13 | + <v-btn icon v-bind="props"> |
| 14 | + <v-icon>{{ getThemeIcon() }}</v-icon> |
| 15 | + </v-btn> |
| 16 | + </template> |
| 17 | + <v-list> |
| 18 | + <v-list-item v-for="mode in themeModes" :key="mode.value" @click="setThemeMode(mode.value)" |
| 19 | + :class="{ 'v-list-item--active': themeMode === mode.value }"> |
| 20 | + <template v-slot:prepend> |
| 21 | + <v-icon>{{ mode.icon }}</v-icon> |
| 22 | + </template> |
| 23 | + <v-list-item-title>{{ mode.title }}</v-list-item-title> |
| 24 | + </v-list-item> |
| 25 | + </v-list> |
| 26 | + </v-menu> |
8 | 27 | </v-app-bar> |
9 | 28 |
|
10 | 29 | <v-main> |
|
32 | 51 | </v-container> |
33 | 52 | </v-main> |
34 | 53 |
|
35 | | - |
36 | | - |
37 | 54 | <NotificationSnackbar v-model="showSnackbar" :text="snackbarText" :color="snackbarColor" /> |
38 | 55 | </v-app> |
39 | 56 | </template> |
40 | 57 |
|
41 | 58 | <script setup> |
42 | | -import { ref } from 'vue' |
| 59 | +import { ref, computed, onMounted, onUnmounted, watch } from 'vue' |
| 60 | +import { useTheme } from 'vuetify' |
43 | 61 | import AppDataCleaner from './components/AppDataCleaner.vue' |
44 | 62 | import DeveloperCleaner from './components/DeveloperCleaner.vue' |
45 | 63 | import NotificationSnackbar from './components/NotificationSnackbar.vue' |
46 | 64 |
|
| 65 | +// Theme functionality |
| 66 | +const theme = useTheme() |
| 67 | +const themeMode = ref('system') // 'light', 'dark', 'system' |
| 68 | +let mediaQuery = null |
| 69 | +
|
| 70 | +// Theme mode options |
| 71 | +const themeModes = [ |
| 72 | + { value: 'light', title: 'Light', icon: 'mdi-brightness-7' }, |
| 73 | + { value: 'dark', title: 'Dark', icon: 'mdi-brightness-4' }, |
| 74 | + { value: 'system', title: 'System', icon: 'mdi-brightness-auto' } |
| 75 | +] |
| 76 | +
|
| 77 | +// Get system theme preference |
| 78 | +const getSystemTheme = () => { |
| 79 | + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' |
| 80 | +} |
| 81 | +
|
| 82 | +// Apply theme based on mode |
| 83 | +const applyTheme = () => { |
| 84 | + if (themeMode.value === 'system') { |
| 85 | + theme.global.name.value = getSystemTheme() |
| 86 | + } else { |
| 87 | + theme.global.name.value = themeMode.value |
| 88 | + } |
| 89 | +} |
| 90 | +
|
| 91 | +// Get appropriate icon for current theme |
| 92 | +const getThemeIcon = () => { |
| 93 | + if (themeMode.value === 'system') { |
| 94 | + return 'mdi-brightness-auto' |
| 95 | + } |
| 96 | + return theme.global.name.value === 'dark' ? 'mdi-brightness-4' : 'mdi-brightness-7' |
| 97 | +} |
| 98 | +
|
| 99 | +// Set theme mode |
| 100 | +const setThemeMode = (mode) => { |
| 101 | + themeMode.value = mode |
| 102 | + applyTheme() |
| 103 | +} |
| 104 | +
|
| 105 | +// Handle system theme changes |
| 106 | +const handleSystemThemeChange = (e) => { |
| 107 | + if (themeMode.value === 'system') { |
| 108 | + theme.global.name.value = e.matches ? 'dark' : 'light' |
| 109 | + } |
| 110 | +} |
| 111 | +
|
| 112 | +// Load theme mode from localStorage on startup |
| 113 | +onMounted(() => { |
| 114 | + const savedThemeMode = localStorage.getItem('devnullifier-theme-mode') |
| 115 | + if (savedThemeMode && ['light', 'dark', 'system'].includes(savedThemeMode)) { |
| 116 | + themeMode.value = savedThemeMode |
| 117 | + } |
| 118 | +
|
| 119 | + // Apply initial theme |
| 120 | + applyTheme() |
| 121 | +
|
| 122 | + // Set up system theme change listener |
| 123 | + mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') |
| 124 | + mediaQuery.addEventListener('change', handleSystemThemeChange) |
| 125 | +}) |
| 126 | +
|
| 127 | +// Cleanup on unmount |
| 128 | +onUnmounted(() => { |
| 129 | + if (mediaQuery) { |
| 130 | + mediaQuery.removeEventListener('change', handleSystemThemeChange) |
| 131 | + } |
| 132 | +}) |
| 133 | +
|
| 134 | +// Watch for theme mode changes and save to localStorage |
| 135 | +watch(themeMode, (newMode) => { |
| 136 | + localStorage.setItem('devnullifier-theme-mode', newMode) |
| 137 | +}) |
| 138 | +
|
47 | 139 | // Reactive state |
48 | 140 | const activeTab = ref('appdata') |
49 | 141 | const showSnackbar = ref(false) |
|
0 commit comments