feat(app): add light theme setting
This commit is contained in:
parent
7ba678117f
commit
0d16354c89
|
|
@ -96,6 +96,12 @@ pub fn install_app_menu<R: tauri::Runtime>(app: &mut tauri::App<R>) -> tauri::Re
|
|||
)
|
||||
.build()?;
|
||||
|
||||
let theme_menu = SubmenuBuilder::new(app, "Theme")
|
||||
.item(&MenuItemBuilder::new("Light").id("theme-light").build(app)?)
|
||||
.item(&MenuItemBuilder::new("Dark").id("theme-dark").build(app)?)
|
||||
.item(&MenuItemBuilder::new("Auto").id("theme-auto").build(app)?)
|
||||
.build()?;
|
||||
|
||||
let view_menu = SubmenuBuilder::new(app, "View")
|
||||
.item(
|
||||
&MenuItemBuilder::new("Zoom In")
|
||||
|
|
@ -141,6 +147,9 @@ pub fn install_app_menu<R: tauri::Runtime>(app: &mut tauri::App<R>) -> tauri::Re
|
|||
.accelerator("CmdOrCtrl+\\")
|
||||
.build(app)?,
|
||||
)
|
||||
.separator()
|
||||
.item(&theme_menu)
|
||||
.separator()
|
||||
.item(&PredefinedMenuItem::fullscreen(app, None)?)
|
||||
.separator()
|
||||
.item(
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ import { provideEditor } from '@open-pencil/vue'
|
|||
import AppToast from '@/components/AppToast.vue'
|
||||
import { useEditorStore } from '@/app/editor/active-store'
|
||||
import { toast } from '@/app/shell/ui'
|
||||
import { useAppTheme } from '@/app/shell/theme'
|
||||
|
||||
useHead({ titleTemplate: (title) => (title ? `${title} — OpenPencil` : 'OpenPencil') })
|
||||
|
||||
const store = useEditorStore()
|
||||
provideEditor(store)
|
||||
useAppTheme()
|
||||
|
||||
onMounted(() => {
|
||||
toast.setupGlobalErrorHandler()
|
||||
|
|
|
|||
12
src/app.css
12
src/app.css
|
|
@ -40,6 +40,18 @@
|
|||
--color-component: #9747ff;
|
||||
}
|
||||
|
||||
html[data-theme='light'] {
|
||||
--color-panel: #ffffff;
|
||||
--color-canvas: #f3f4f6;
|
||||
--color-border: #d8dce3;
|
||||
--color-hover: #eef1f5;
|
||||
--color-accent: #2563eb;
|
||||
--color-surface: #202124;
|
||||
--color-muted: #6b7280;
|
||||
--color-input: #ffffff;
|
||||
--color-component: #7c3aed;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { computed } from 'vue'
|
|||
|
||||
import { useEditorStore } from '@/app/editor/active-store'
|
||||
import { openFileDialog } from '@/app/shell/menu/use'
|
||||
import { useAppTheme } from '@/app/shell/theme'
|
||||
import { useEditorCommands, useI18n } from '@open-pencil/vue'
|
||||
|
||||
import type { MenuEntry } from '@open-pencil/vue'
|
||||
|
|
@ -15,6 +16,7 @@ export function useAppMenu(mod: string) {
|
|||
const store = useEditorStore()
|
||||
const { menuItem: commandMenuItem } = useEditorCommands()
|
||||
const { menu: t, panels: p, locale, availableLocales, localeLabels, setLocale } = useI18n()
|
||||
const { theme, setTheme } = useAppTheme()
|
||||
|
||||
const languageMenu = computed<MenuEntry[]>(() =>
|
||||
availableLocales.map((code) => ({
|
||||
|
|
@ -123,6 +125,32 @@ export function useAppMenu(mod: string) {
|
|||
}
|
||||
},
|
||||
{ separator: true as const },
|
||||
{
|
||||
label: 'Theme',
|
||||
sub: [
|
||||
{
|
||||
label: 'Light',
|
||||
checked: theme.value === 'light',
|
||||
onCheckedChange: (checked: boolean) => {
|
||||
if (checked) setTheme('light')
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Dark',
|
||||
checked: theme.value === 'dark',
|
||||
onCheckedChange: (checked: boolean) => {
|
||||
if (checked) setTheme('dark')
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Auto',
|
||||
checked: theme.value === 'auto',
|
||||
onCheckedChange: (checked: boolean) => {
|
||||
if (checked) setTheme('auto')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: t.value.language,
|
||||
sub: languageMenu.value
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { tryOnScopeDispose } from '@vueuse/core'
|
|||
|
||||
import { useEditorStore } from '@/app/editor/active-store'
|
||||
import { importFileDialog, openFileDialog } from '@/app/shell/menu/files'
|
||||
import { useAppTheme } from '@/app/shell/theme'
|
||||
import { createTab, closeTab, activeTab } from '@/app/tabs'
|
||||
import { IS_TAURI } from '@/constants'
|
||||
|
||||
|
|
@ -38,10 +39,16 @@ export function useMenu() {
|
|||
if (!IS_TAURI) return
|
||||
|
||||
let unlisten: (() => void) | undefined
|
||||
const { setTheme } = useAppTheme()
|
||||
const themeActions: Partial<Record<string, () => void>> = {
|
||||
'theme-dark': () => setTheme('dark'),
|
||||
'theme-light': () => setTheme('light'),
|
||||
'theme-auto': () => setTheme('auto')
|
||||
}
|
||||
|
||||
void import('@tauri-apps/api/event').then(({ listen }) => {
|
||||
void listen<string>('menu-event', (event) => {
|
||||
const action = MENU_ACTIONS[event.payload]
|
||||
const action = MENU_ACTIONS[event.payload] ?? themeActions[event.payload]
|
||||
if (action) action()
|
||||
}).then((fn) => {
|
||||
unlisten = fn
|
||||
|
|
|
|||
41
src/app/shell/theme.ts
Normal file
41
src/app/shell/theme.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { useLocalStorage, usePreferredDark } from '@vueuse/core'
|
||||
import { computed, watchEffect } from 'vue'
|
||||
|
||||
import { IS_BROWSER } from '@open-pencil/core/constants'
|
||||
|
||||
export type AppTheme = 'dark' | 'light' | 'auto'
|
||||
|
||||
const THEME_STORAGE_KEY = 'open-pencil:theme'
|
||||
const DEFAULT_THEME: AppTheme = 'dark'
|
||||
|
||||
const theme = useLocalStorage<AppTheme>(THEME_STORAGE_KEY, DEFAULT_THEME)
|
||||
const prefersDark = usePreferredDark()
|
||||
const resolvedTheme = computed<'dark' | 'light'>(() => {
|
||||
if (theme.value === 'auto') return prefersDark.value ? 'dark' : 'light'
|
||||
return theme.value
|
||||
})
|
||||
|
||||
function applyTheme(value: 'dark' | 'light', setting: AppTheme): void {
|
||||
if (!IS_BROWSER) return
|
||||
document.documentElement.dataset.theme = value
|
||||
document.documentElement.dataset.themeSetting = setting
|
||||
document.documentElement.style.colorScheme = value
|
||||
}
|
||||
|
||||
export function useAppTheme() {
|
||||
watchEffect(() => applyTheme(resolvedTheme.value, theme.value))
|
||||
|
||||
const isLight = computed(() => resolvedTheme.value === 'light')
|
||||
|
||||
function setTheme(value: AppTheme): void {
|
||||
theme.value = value
|
||||
}
|
||||
|
||||
function toggleTheme(): void {
|
||||
theme.value = isLight.value ? 'dark' : 'light'
|
||||
}
|
||||
|
||||
return { theme, resolvedTheme, isLight, setTheme, toggleTheme }
|
||||
}
|
||||
|
||||
applyTheme(resolvedTheme.value, theme.value)
|
||||
|
|
@ -30,7 +30,7 @@ function onClose(e: MouseEvent, tabId: string) {
|
|||
v-if="tabs.length > 1"
|
||||
v-model="modelValue"
|
||||
activation-mode="automatic"
|
||||
class="scrollbar-none flex h-9 shrink-0 items-end overflow-x-auto border-b border-border bg-[#1e1e1e]"
|
||||
class="scrollbar-none flex h-9 shrink-0 items-end overflow-x-auto border-b border-border bg-canvas"
|
||||
>
|
||||
<TabsList class="flex h-full items-end">
|
||||
<TabsTrigger
|
||||
|
|
|
|||
Loading…
Reference in a new issue