Files
modrinth/apps/app-frontend/src/components/ui/settings/display/AppearanceSettings.vue
T
Prospector b68a199814 menu refactor + web account switcher (#7307)
* add account switcher, migrate context menus to overflow menu system, update version filter controls, only pad instance icons

* app account switcher, improve some context menus, theme stuff

* prepr

* handle reauthentication

* fix a couple sign in issues, admin page cleanup, fix org status badges, fix official account badge, open in local keybind, publish plus icons

* rename generic menus to ButtonMenu (& types)

* fix hydration issue w/ dropdowns + middleware error
2026-08-27 00:35:34 +00:00

163 lines
4.2 KiB
Vue

<script setup lang="ts">
import {
AppearanceSettingsLayout,
injectAuth,
injectUserPreferences,
provideAppearanceSettings,
useSavable,
} from '@modrinth/ui'
import { computed, inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { type ColorTheme, isDarkTheme, useTheme } from '@/composables/use-theme.ts'
import { type AppSettings, get, set } from '@/helpers/settings.ts'
import { getOS } from '@/helpers/utils'
import { appSettingsModalContextKey } from '@/providers/app-settings-modal'
const theme = useTheme()
const auth = injectAuth()
const { updatePreferences } = injectUserPreferences()
const settingsModal = inject(appSettingsModalContextKey, null)
const os = await getOS()
const settings = ref(await get())
type AppearanceSettingsState = {
theme: ColorTheme
syncAcrossDevices: boolean
advancedRendering: boolean
nativeDecorations: boolean
}
function getAppearanceSettingsState(settings: AppSettings): AppearanceSettingsState {
return {
theme: settings.theme,
syncAcrossDevices: settings.sync_theme_across_devices,
advancedRendering: settings.advanced_rendering,
nativeDecorations: settings.native_decorations,
}
}
const { saved, current, changes, saving, hasChanges, reset, save } = useSavable(
() => getAppearanceSettingsState(settings.value),
async (appearanceChanges) => {
const value = current.value
if (
value.syncAcrossDevices &&
auth.user.value &&
(appearanceChanges.theme !== undefined || appearanceChanges.syncAcrossDevices !== undefined)
) {
await updatePreferences({
appearance: value.theme === 'system' ? { auto: true } : { auto: false, theme: value.theme },
})
}
const nextSettings: AppSettings = {
...settings.value,
theme: value.theme,
sync_theme_across_devices: value.syncAcrossDevices,
advanced_rendering: value.advancedRendering,
native_decorations: value.nativeDecorations,
}
await set(nextSettings)
settings.value = nextSettings
if (isDarkTheme(value.theme)) {
theme.preferredDark = value.theme
}
theme.preferred = value.theme
theme.syncAcrossDevices = value.syncAcrossDevices
theme.advancedRendering = value.advancedRendering
},
)
const themeOptions = computed(() =>
theme.options.filter(
(option) =>
option !== 'retro' || settings.value.developer_mode || current.value.theme === 'retro',
),
)
const preferredDarkTheme = computed(() =>
isDarkTheme(current.value.theme) ? current.value.theme : theme.preferredDark,
)
function setTheme(value: ColorTheme): void {
current.value.theme = value
}
function setSyncAcrossDevices(enabled: boolean): void {
current.value.syncAcrossDevices = enabled
}
function setAdvancedRendering(enabled: boolean): void {
current.value.advancedRendering = enabled
}
function setNativeDecorations(enabled: boolean): void {
current.value.nativeDecorations = enabled
}
watch(
[() => current.value.theme, () => saved.value.theme],
([selectedTheme, savedTheme]) => {
theme.preview = selectedTheme === savedTheme ? null : selectedTheme
},
{ immediate: true },
)
async function saveAppearanceSettings(): Promise<void> {
try {
await save()
} catch {
return
}
}
onMounted(() => {
settingsModal?.registerUnsavedChangesController({
hasChanges: () => hasChanges.value,
getOriginal: () => saved.value,
getModified: () => changes.value,
isSaving: () => saving.value,
reset,
save: saveAppearanceSettings,
})
})
onBeforeUnmount(() => {
theme.preview = null
settingsModal?.registerUnsavedChangesController(null)
})
provideAppearanceSettings({
deferPersistence: true,
theme: {
current: computed(() => current.value.theme),
options: themeOptions,
system: computed(() => (theme.native === 'light' ? 'light' : preferredDarkTheme.value)),
preferredDark: preferredDarkTheme,
set: setTheme,
syncAcrossDevices: {
value: computed(() => current.value.syncAcrossDevices),
set: setSyncAcrossDevices,
},
syncDisabled: computed(() => !auth.user.value),
},
advancedRendering: {
value: computed(() => current.value.advancedRendering),
set: setAdvancedRendering,
},
nativeDecorations:
os !== 'MacOS'
? {
value: computed(() => current.value.nativeDecorations),
set: setNativeDecorations,
}
: undefined,
updatePreferences,
})
</script>
<template>
<AppearanceSettingsLayout />
</template>