import { schema } from '#triplit/schema'; import { type Entity } from '@triplit/client'; import { computed, watch } from 'vue'; // TODO: most of this code is generated by gemini 3 flash with fixups, // but its still bad code, so I'm going to clean it up later export const useUserSettings = async () => { const { user, loggedIn } = useAuth(); const triplit = useTriplitClient(); const accent = useState('theme:accent', () => 'violet'); const neutral = useState('theme:neutral', () => 'zinc'); const hinting = useState('theme:hinting', () => '0'); const colorScheme: { preference: Ref<'light' | 'dark' | 'system'>; value: Ref<'light' | 'dark'>; class: Ref<'light' | 'dark' | undefined> } = { preference: useState('theme:colorScheme', () => 'system' as 'light' | 'dark' | 'system'), value: computed(() => { if (colorScheme.preference.value === 'system') { if (import.meta.client) { const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; return prefersDark ? 'dark' : 'light'; } return 'dark'; } return colorScheme.preference.value as 'light' | 'dark'; }), class: ref(undefined) } let listeningToColorScheme = false; const changeSystemColorScheme = (e: MediaQueryListEvent) => { if (colorScheme.preference.value !== 'system') return; colorScheme.class.value = e.matches ? 'dark' : 'light'; } watch(colorScheme.preference, () => { if (colorScheme.preference.value === 'system') { if (import.meta.client) { const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; if (!listeningToColorScheme) { listeningToColorScheme = true; window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', changeSystemColorScheme); } colorScheme.class.value = prefersDark ? 'dark' : 'light'; return; } if (listeningToColorScheme) { listeningToColorScheme = false; window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', changeSystemColorScheme); } // fallback to dark on the server colorScheme.class.value = 'dark'; return; } colorScheme.class.value = colorScheme.preference.value as 'light' | 'dark'; }, { immediate: true }); const start = Date.now(); const remoteSettings = useState | null>('user:settings', () => null); const { results } = await useQuery('settings', triplit, triplit.query('settings')) console.log("fetching settings took", Date.now() - start); watch(results, (val) => { if (!val) { remoteSettings.value = null; return; } remoteSettings.value = val[0] as any; }, { immediate: true, deep: true }) watch(remoteSettings, (newSettings) => { if (!newSettings?.appearance) return; const { appearance } = newSettings; if (appearance.colorScheme) { colorScheme.preference.value = appearance.colorScheme as 'light' | 'dark' | 'system'; } if (appearance.accent) { accent.value = appearance.accent; } if (appearance.neutral) { neutral.value = appearance.neutral; } if (appearance.hinting !== undefined) { hinting.value = String(appearance.hinting); } }, { immediate: true, deep: true }); // Effective settings with defaults for backwards compatibility const settings = computed(() => { const remote = remoteSettings.value; return { appearance: { colorScheme: remote?.appearance?.colorScheme ?? colorScheme.preference.value, accent: remote?.appearance?.accent ?? accent.value, neutral: remote?.appearance?.neutral ?? neutral.value, hinting: remote?.appearance?.hinting ?? Number(hinting.value), fontSize: remote?.appearance?.fontSize ?? 'medium', }, systemAssistants: { rename: { enabled: remote?.systemAssistants?.rename?.enabled ?? false, prompt: remote?.systemAssistants?.rename?.prompt ?? null, modelId: remote?.systemAssistants?.rename?.modelId ?? null, } } }; }); /** * Update settings both locally and on the server */ const updateSettings = async (updates: { appearance?: { colorScheme?: 'light' | 'dark' | 'system'; accent?: string; neutral?: string; hinting?: number; fontSize?: string; }; systemAssistants?: { rename?: { enabled?: boolean; prompt?: string | null; modelId?: string | null; }; }; }) => { if (updates.appearance) { if (updates.appearance.colorScheme) colorScheme.preference.value = updates.appearance.colorScheme; if (updates.appearance.accent) accent.value = updates.appearance.accent; if (updates.appearance.neutral) neutral.value = updates.appearance.neutral; if (updates.appearance.hinting !== undefined) hinting.value = String(updates.appearance.hinting); } if (!loggedIn.value || !user.value?.id) return; const current = remoteSettings.value; if (!current) { await triplit.insert('settings', { userId: user.value.id, appearance: { colorScheme: colorScheme.preference.value, accent: accent.value, neutral: neutral.value, hinting: Number(hinting.value), ...(updates.appearance || {}) }, systemAssistants: { rename: { enabled: updates.systemAssistants?.rename?.enabled ?? true, prompt: updates.systemAssistants?.rename?.prompt ?? null, modelId: updates.systemAssistants?.rename?.modelId ?? null, } } }); return; } await triplit.update('settings', current.id, (s) => { if (updates.appearance) { s.appearance = { ...(s.appearance || {}), ...updates.appearance }; } if (updates.systemAssistants) { // @ts-expect-error s.systemAssistants = { ...(s.systemAssistants || {}), ...updates.systemAssistants }; } }); }; return { settings, remoteSettings, updateSettings, accent, neutral, hinting, colorScheme }; };