refactor: useUserSettings uses global state to reduce db calls

This commit is contained in:
Zoe
2026-03-01 20:29:37 -06:00
parent e0914b684a
commit 5a35976ce0
18 changed files with 128 additions and 127 deletions
+1 -1
View File
@@ -448,7 +448,7 @@ export const useChat = (agentId: string) => {
FailedToGenerate,
}
const autoRename = async (topicId: string, prompt: string): Promise<Result<string, AutoRenameError>> => {
const { settings } = await useUserSettings();
const { settings } = useUserSettings();
console.log(settings.value);
-1
View File
@@ -1,4 +1,3 @@
// // composables/useModels.ts
import type { Entity } from '@triplit/client';
import type schema from '#triplit/schema';
+105 -106
View File
@@ -1,112 +1,109 @@
import { schema } from '#triplit/schema';
import { type Entity } from '@triplit/client';
import { computed, watch } from 'vue';
import { computed, watch, ref, type Ref } 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();
type UserSettings = Entity<typeof schema, 'settings'>;
interface UserSettingsState {
accent: Ref<string>;
neutral: Ref<string>;
hinting: Ref<string>;
colorSchemePreference: Ref<'light' | 'dark' | 'system'>;
colorSchemeClass: Ref<'light' | 'dark' | undefined>;
remoteSettings: Ref<UserSettings | null>;
initPromise: Promise<void> | null;
}
export const useUserSettings = () => {
const nuxtApp = useNuxtApp();
const triplit = useTriplitClient();
const { user, loggedIn } = useAuth();
const accent = useState('theme:accent', () => 'violet');
const neutral = useState('theme:neutral', () => 'zinc');
const hinting = useState('theme:hinting', () => '0');
if (!nuxtApp._userSettingsState) {
nuxtApp._userSettingsState = {
accent: ref('violet'),
neutral: ref('zinc'),
hinting: ref('0'),
colorSchemePreference: ref<'light' | 'dark' | 'system'>('system'),
colorSchemeClass: ref<undefined | 'light' | 'dark'>(undefined),
remoteSettings: ref<UserSettings | null>(null),
initPromise: null,
} as UserSettingsState;
}
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;
const state = nuxtApp._userSettingsState as UserSettingsState;
return prefersDark ? 'dark' : 'light';
const init = () => {
if (state.initPromise) return state.initPromise;
state.initPromise = (async () => {
const { results } = await useQuery('settings', triplit, triplit.query('settings'));
watch(results, (val) => {
if (val && val.length > 0) {
state.remoteSettings.value = val[0] as UserSettings;
} else {
state.remoteSettings.value = null;
}
}, { immediate: true, deep: true });
return 'dark';
}
watch(state.remoteSettings, (newSettings) => {
if (!newSettings?.appearance) return;
const { appearance } = newSettings;
return colorScheme.preference.value as 'light' | 'dark';
}),
class: ref<undefined | 'light' | 'dark'>(undefined)
}
if (appearance.colorScheme) {
state.colorSchemePreference.value = appearance.colorScheme as 'light' | 'dark' | 'system';
}
if (appearance.accent) {
state.accent.value = appearance.accent;
}
if (appearance.neutral) {
state.neutral.value = appearance.neutral;
}
if (appearance.hinting !== undefined) {
state.hinting.value = String(appearance.hinting);
}
}, { immediate: true, deep: true });
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;
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const update = () => {
if (state.colorSchemePreference.value === 'system') {
state.colorSchemeClass.value = mediaQuery.matches ? 'dark' : 'light';
} else {
state.colorSchemeClass.value = state.colorSchemePreference.value as 'dark' | 'light';
}
};
mediaQuery.addEventListener('change', update);
watch(state.colorSchemePreference, update, { immediate: true });
} else {
watch(state.colorSchemePreference, (pref) => {
state.colorSchemeClass.value = pref === 'system' ? 'dark' : (pref as 'dark' | 'light');
}, { immediate: true });
}
})();
if (listeningToColorScheme) {
listeningToColorScheme = false;
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', changeSystemColorScheme);
return state.initPromise;
};
const colorSchemeValue = computed(() => {
if (state.colorSchemePreference.value === 'system') {
if (import.meta.client) {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
// fallback to dark on the server
colorScheme.class.value = 'dark';
return;
return 'dark';
}
return state.colorSchemePreference.value as 'dark' | 'light';
});
colorScheme.class.value = colorScheme.preference.value as 'light' | 'dark';
}, { immediate: true });
const start = Date.now();
const remoteSettings = useState<Entity<typeof schema, 'settings'> | 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;
const remote = state.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),
colorScheme: remote?.appearance?.colorScheme ?? state.colorSchemePreference.value,
accent: remote?.appearance?.accent ?? state.accent.value,
neutral: remote?.appearance?.neutral ?? state.neutral.value,
hinting: remote?.appearance?.hinting ?? Number(state.hinting.value),
fontSize: remote?.appearance?.fontSize ?? 'medium',
},
systemAssistants: {
@@ -119,9 +116,6 @@ export const useUserSettings = async () => {
};
});
/**
* Update settings both locally and on the server
*/
const updateSettings = async (updates: {
appearance?: {
colorScheme?: 'light' | 'dark' | 'system';
@@ -139,23 +133,23 @@ export const useUserSettings = async () => {
};
}) => {
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 (updates.appearance.colorScheme) state.colorSchemePreference.value = updates.appearance.colorScheme;
if (updates.appearance.accent) state.accent.value = updates.appearance.accent;
if (updates.appearance.neutral) state.neutral.value = updates.appearance.neutral;
if (updates.appearance.hinting !== undefined) state.hinting.value = String(updates.appearance.hinting);
}
if (!loggedIn.value || !user.value?.id) return;
const current = remoteSettings.value;
const current = state.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),
colorScheme: state.colorSchemePreference.value,
accent: state.accent.value,
neutral: state.neutral.value,
hinting: Number(state.hinting.value),
...(updates.appearance || {})
},
systemAssistants: {
@@ -187,12 +181,17 @@ export const useUserSettings = async () => {
};
return {
init,
settings,
remoteSettings,
remoteSettings: state.remoteSettings,
updateSettings,
accent,
neutral,
hinting,
colorScheme
accent: state.accent,
neutral: state.neutral,
hinting: state.hinting,
colorScheme: {
preference: state.colorSchemePreference,
value: colorSchemeValue,
class: state.colorSchemeClass
}
};
};