/** * 界面偏好,存在 localStorage 里。 * * 密度:工具栏和导航是只显示图标,还是图标加文字。 * 默认是「完整」——这个工具是隔一阵子才动一次的,纯图标对偶尔来一次的人不友好; * 想收拢的人用侧栏底部的按钮切。 * * 主题:跟随系统、浅色、深色。默认跟随系统。 * * localStorage 和 document 都加了存在性守卫:api.ts 引用这里(测速地址), * 而 api.ts 被 bun test 环境里跑的模块间接引用——那里没有浏览器全局, * 模块顶层直接碰它们会把整个测试文件炸掉。 */ import { ref, watch } from 'vue' const storage = typeof localStorage === 'undefined' ? null : localStorage export type Density = 'full' | 'compact' const KEY = 'patchbay.density' const stored = storage?.getItem(KEY) export const density = ref(stored === 'compact' ? 'compact' : 'full') watch(density, (value) => { storage?.setItem(KEY, value) }) export function toggleDensity(): void { density.value = density.value === 'full' ? 'compact' : 'full' } /** 测延迟用的目标地址。204 空响应的端点最合适,默认值全球都有部署。 */ export const DEFAULT_TEST_URL = 'https://www.gstatic.com/generate_204' const TEST_URL_KEY = 'patchbay.test-url' export const testUrl = ref(storage?.getItem(TEST_URL_KEY) || DEFAULT_TEST_URL) watch(testUrl, (value) => { if (value.trim() === '' || value === DEFAULT_TEST_URL) storage?.removeItem(TEST_URL_KEY) else storage?.setItem(TEST_URL_KEY, value.trim()) }) export type Theme = 'system' | 'light' | 'dark' const THEME_KEY = 'patchbay.theme' const storedTheme = storage?.getItem(THEME_KEY) export const theme = ref( storedTheme === 'light' || storedTheme === 'dark' ? storedTheme : 'system', ) // 显式选了浅色或深色就在 上标出来:tokens.css 里 :root[data-theme=…] 的权重 // 高于 :root 和媒体查询里的 :root,压过系统偏好。跟随系统则不加标记,交回给媒体查询, // 这样系统主题切换时界面仍然跟着变。 watch( theme, (value) => { storage?.setItem(THEME_KEY, value) if (typeof document === 'undefined') return if (value === 'system') document.documentElement.removeAttribute('data-theme') else document.documentElement.setAttribute('data-theme', value) }, { immediate: true }, )