mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 12:05:53 +00:00
feat: preferences syncing frontend (#7192)
* feat: prefs frontend * fix: DI issue * fix: lint * feat: appearance settings cleanup + fix settings, remove pinia * fix: sync btn when logged out * fix: prepr * fix: sidebar issue * feat: language coverage + cleanup * feat: cleanup lang settings * fix: fmt * fix: CI * fix: ci * fix: storybook * feat: bring back loader/game version sort --------- Co-authored-by: tdgao <mr.trumgao@gmail.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<AccountSocialSettings
|
||||
ref="socialSettings"
|
||||
:get-blocked-users="get_blocked_users"
|
||||
:get-users="getUsers"
|
||||
:unblock-user="unblock_user"
|
||||
@@ -9,10 +10,29 @@
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { AccountSocialSettings, injectModrinthClient } from '@modrinth/ui'
|
||||
import { inject, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
import { get_blocked_users, unblock_user } from '@/helpers/users'
|
||||
import { appSettingsModalContextKey } from '@/providers/app-settings-modal'
|
||||
|
||||
const client = injectModrinthClient()
|
||||
const settingsModal = inject(appSettingsModalContextKey, null)
|
||||
const socialSettings = ref<InstanceType<typeof AccountSocialSettings> | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
settingsModal?.registerUnsavedChangesController({
|
||||
hasChanges: () => socialSettings.value?.hasChanges ?? false,
|
||||
getOriginal: () => socialSettings.value?.originalState ?? {},
|
||||
getModified: () => socialSettings.value?.modifiedState ?? {},
|
||||
isSaving: () => socialSettings.value?.saving ?? false,
|
||||
reset: () => socialSettings.value?.reset(),
|
||||
save: () => socialSettings.value?.save(),
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
settingsModal?.registerUnsavedChangesController(null)
|
||||
})
|
||||
|
||||
function getUsers(userIds: string[]): Promise<Labrinth.Users.v2.User[]> {
|
||||
return client.labrinth.users_v2.getMultiple(userIds)
|
||||
|
||||
@@ -1,108 +1,154 @@
|
||||
<script setup lang="ts">
|
||||
import { defineMessages, ThemeSelector, Toggle, useVIntl } from '@modrinth/ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import {
|
||||
AppearanceSettingsLayout,
|
||||
injectAuth,
|
||||
injectUserPreferences,
|
||||
provideAppearanceSettings,
|
||||
useSavable,
|
||||
} from '@modrinth/ui'
|
||||
import { computed, inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import { type ColorTheme, useTheme } from '@/composables/use-theme.ts'
|
||||
import { type AppSettings, get, set } from '@/helpers/settings.ts'
|
||||
import { getOS } from '@/helpers/utils'
|
||||
import { useTheming } from '@/store/state'
|
||||
import type { ColorTheme } from '@/store/theme.ts'
|
||||
import { appSettingsModalContextKey } from '@/providers/app-settings-modal'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
colorThemeTitle: {
|
||||
id: 'app.appearance-settings.color-theme.title',
|
||||
defaultMessage: 'Color theme',
|
||||
},
|
||||
colorThemeDescription: {
|
||||
id: 'app.appearance-settings.color-theme.description',
|
||||
defaultMessage: 'Choose the color theme used by Modrinth App.',
|
||||
},
|
||||
advancedRenderingTitle: {
|
||||
id: 'app.appearance-settings.advanced-rendering.title',
|
||||
defaultMessage: 'Advanced rendering',
|
||||
},
|
||||
advancedRenderingDescription: {
|
||||
id: 'app.appearance-settings.advanced-rendering.description',
|
||||
defaultMessage:
|
||||
'Enable visual effects such as background blur. This may reduce performance without hardware acceleration.',
|
||||
},
|
||||
nativeDecorationsTitle: {
|
||||
id: 'app.appearance-settings.native-decorations.title',
|
||||
defaultMessage: 'System window frame',
|
||||
},
|
||||
nativeDecorationsDescription: {
|
||||
id: 'app.appearance-settings.native-decorations.description',
|
||||
defaultMessage:
|
||||
"Use your operating system's title bar and window controls. Requires an app restart.",
|
||||
},
|
||||
})
|
||||
|
||||
const os = ref(await getOS())
|
||||
const theme = useTheme()
|
||||
const auth = injectAuth()
|
||||
const { updatePreferences } = injectUserPreferences()
|
||||
const settingsModal = inject(appSettingsModalContextKey, null)
|
||||
const os = await getOS()
|
||||
const settings = ref(await get())
|
||||
const themeOptions = computed(() =>
|
||||
themeStore
|
||||
.getThemeOptions()
|
||||
.filter((theme) => theme !== 'retro' || themeStore.devMode || settings.value.theme === 'retro'),
|
||||
|
||||
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
|
||||
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',
|
||||
),
|
||||
)
|
||||
|
||||
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(
|
||||
settings,
|
||||
async () => {
|
||||
await set(settings.value)
|
||||
[() => current.value.theme, () => saved.value.theme],
|
||||
([selectedTheme, savedTheme]) => {
|
||||
theme.preview = selectedTheme === savedTheme ? null : selectedTheme
|
||||
},
|
||||
{ deep: true },
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<h2 class="m-0 text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(messages.colorThemeTitle) }}
|
||||
</h2>
|
||||
|
||||
<p class="m-0 mt-1">{{ formatMessage(messages.colorThemeDescription) }}</p>
|
||||
async function saveAppearanceSettings(): Promise<void> {
|
||||
try {
|
||||
await save()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
<ThemeSelector
|
||||
:update-color-theme="
|
||||
(theme: ColorTheme) => {
|
||||
themeStore.setThemeState(theme)
|
||||
settings.theme = theme
|
||||
}
|
||||
"
|
||||
:current-theme="settings.theme"
|
||||
:theme-options="themeOptions"
|
||||
system-theme-color="system"
|
||||
/>
|
||||
onMounted(() => {
|
||||
settingsModal?.registerUnsavedChangesController({
|
||||
hasChanges: () => hasChanges.value,
|
||||
getOriginal: () => saved.value,
|
||||
getModified: () => changes.value,
|
||||
isSaving: () => saving.value,
|
||||
reset,
|
||||
save: saveAppearanceSettings,
|
||||
})
|
||||
})
|
||||
|
||||
<div class="mt-6 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="m-0 text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(messages.advancedRenderingTitle) }}
|
||||
</h2>
|
||||
<p class="m-0 mt-1">
|
||||
{{ formatMessage(messages.advancedRenderingDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
onBeforeUnmount(() => {
|
||||
theme.preview = null
|
||||
settingsModal?.registerUnsavedChangesController(null)
|
||||
})
|
||||
|
||||
<Toggle
|
||||
id="advanced-rendering"
|
||||
:model-value="themeStore.advancedRendering"
|
||||
@update:model-value="
|
||||
(e) => {
|
||||
themeStore.advancedRendering = !!e
|
||||
settings.advanced_rendering = themeStore.advancedRendering
|
||||
provideAppearanceSettings({
|
||||
deferPersistence: true,
|
||||
theme: {
|
||||
current: computed(() => current.value.theme),
|
||||
options: themeOptions,
|
||||
system: computed(() => theme.native),
|
||||
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,
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
: undefined,
|
||||
updatePreferences,
|
||||
})
|
||||
</script>
|
||||
|
||||
<div v-if="os !== 'MacOS'" class="mt-6 flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="m-0 text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(messages.nativeDecorationsTitle) }}
|
||||
</h2>
|
||||
<p class="m-0 mt-1">{{ formatMessage(messages.nativeDecorationsDescription) }}</p>
|
||||
</div>
|
||||
<Toggle id="native-decorations" v-model="settings.native_decorations" />
|
||||
</div>
|
||||
<template>
|
||||
<AppearanceSettingsLayout />
|
||||
</template>
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { defineMessages, Toggle, useVIntl } from '@modrinth/ui'
|
||||
import { ref, watch } from 'vue'
|
||||
import {
|
||||
defineMessages,
|
||||
injectAuth,
|
||||
injectUserPreferences,
|
||||
Toggle,
|
||||
useSavable,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { inject, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import { useTheming } from '@/store/state'
|
||||
import type { FeatureFlag } from '@/store/theme.ts'
|
||||
import {
|
||||
DEFAULT_FEATURE_FLAGS,
|
||||
type FeatureFlag,
|
||||
useAppSettings,
|
||||
} from '@/composables/use-app-settings.ts'
|
||||
import { type AppSettings, get, set } from '@/helpers/settings.ts'
|
||||
import { appSettingsModalContextKey } from '@/providers/app-settings-modal'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const { formatMessage } = useVIntl()
|
||||
const auth = injectAuth()
|
||||
const { updatePreferences } = injectUserPreferences()
|
||||
const settingsModal = inject(appSettingsModalContextKey, null)
|
||||
|
||||
const worldsInHomeFlag: FeatureFlag = 'worlds_in_home'
|
||||
const skipNonEssentialWarningsFlag: FeatureFlag = 'skip_non_essential_warnings'
|
||||
@@ -15,6 +29,19 @@ const skipUnknownPackWarningFlag: FeatureFlag = 'skip_unknown_pack_warning'
|
||||
const showPlayTimeFlag: FeatureFlag = 'show_instance_play_time'
|
||||
|
||||
const messages = defineMessages({
|
||||
syncAcrossDevicesTitle: {
|
||||
id: 'app.behavior-settings.sync-across-devices.title',
|
||||
defaultMessage: 'Sync behavior across devices',
|
||||
},
|
||||
syncAcrossDevicesDescription: {
|
||||
id: 'app.behavior-settings.sync-across-devices.description',
|
||||
defaultMessage:
|
||||
"Use these behavior settings everywhere you're signed in. Turn this off to keep separate settings on this device.",
|
||||
},
|
||||
syncAcrossDevicesSignedOutTooltip: {
|
||||
id: 'app.behavior-settings.sync-across-devices.signed-out-tooltip',
|
||||
defaultMessage: 'Sign into a Modrinth account to sync settings.',
|
||||
},
|
||||
startupAndNavigationTitle: {
|
||||
id: 'app.behavior-settings.startup-and-navigation.title',
|
||||
defaultMessage: 'Startup and navigation',
|
||||
@@ -96,18 +123,136 @@ const messages = defineMessages({
|
||||
},
|
||||
})
|
||||
|
||||
const settings = ref(await get())
|
||||
type BehaviorSettingsState = {
|
||||
syncBehaviorAcrossDevices: boolean
|
||||
minimizeApp: boolean
|
||||
hideRightSidebar: boolean
|
||||
showJumpIn: boolean
|
||||
showPlayTime: boolean
|
||||
hideNametag: boolean
|
||||
warnOnUnknownModpacks: boolean
|
||||
skipNonEssentialWarnings: boolean
|
||||
}
|
||||
|
||||
watch(
|
||||
settings,
|
||||
const persistedSettings = ref(await get())
|
||||
|
||||
function getBehaviorSettingsState(settings: AppSettings): BehaviorSettingsState {
|
||||
return {
|
||||
syncBehaviorAcrossDevices: settings.sync_behavior_across_devices,
|
||||
minimizeApp: settings.hide_on_process_start,
|
||||
hideRightSidebar: settings.toggle_sidebar,
|
||||
showJumpIn: settings.feature_flags[worldsInHomeFlag] ?? DEFAULT_FEATURE_FLAGS[worldsInHomeFlag],
|
||||
showPlayTime:
|
||||
settings.feature_flags[showPlayTimeFlag] ?? DEFAULT_FEATURE_FLAGS[showPlayTimeFlag],
|
||||
hideNametag: settings.hide_nametag_skins_page,
|
||||
warnOnUnknownModpacks: !(
|
||||
settings.feature_flags[skipUnknownPackWarningFlag] ??
|
||||
DEFAULT_FEATURE_FLAGS[skipUnknownPackWarningFlag]
|
||||
),
|
||||
skipNonEssentialWarnings:
|
||||
settings.feature_flags[skipNonEssentialWarningsFlag] ??
|
||||
DEFAULT_FEATURE_FLAGS[skipNonEssentialWarningsFlag],
|
||||
}
|
||||
}
|
||||
|
||||
const { saved, current, changes, saving, hasChanges, reset, save } = useSavable(
|
||||
() => getBehaviorSettingsState(persistedSettings.value),
|
||||
async () => {
|
||||
await set(settings.value)
|
||||
const value = current.value
|
||||
|
||||
if (value.syncBehaviorAcrossDevices && auth.user.value) {
|
||||
await updatePreferences({
|
||||
behavior: {
|
||||
minimize_app: value.minimizeApp,
|
||||
hide_right_sidebar: value.hideRightSidebar,
|
||||
show_jump_in: value.showJumpIn,
|
||||
show_play_time: value.showPlayTime,
|
||||
hide_nametag: value.hideNametag,
|
||||
warn_on_unknown_modpacks: value.warnOnUnknownModpacks,
|
||||
skip_non_essential_warnings: value.skipNonEssentialWarnings,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const nextSettings: AppSettings = {
|
||||
...persistedSettings.value,
|
||||
sync_behavior_across_devices: value.syncBehaviorAcrossDevices,
|
||||
hide_on_process_start: value.minimizeApp,
|
||||
toggle_sidebar: value.hideRightSidebar,
|
||||
hide_nametag_skins_page: value.hideNametag,
|
||||
feature_flags: {
|
||||
...persistedSettings.value.feature_flags,
|
||||
[worldsInHomeFlag]: value.showJumpIn,
|
||||
[showPlayTimeFlag]: value.showPlayTime,
|
||||
[skipUnknownPackWarningFlag]: !value.warnOnUnknownModpacks,
|
||||
[skipNonEssentialWarningsFlag]: value.skipNonEssentialWarnings,
|
||||
},
|
||||
}
|
||||
|
||||
await set(nextSettings)
|
||||
persistedSettings.value = nextSettings
|
||||
appSettings.setBehaviorSyncAcrossDevices(value.syncBehaviorAcrossDevices)
|
||||
appSettings.toggleSidebar = value.hideRightSidebar
|
||||
appSettings.hideNametagSkinsPage = value.hideNametag
|
||||
appSettings.featureFlags[worldsInHomeFlag] = value.showJumpIn
|
||||
appSettings.featureFlags[showPlayTimeFlag] = value.showPlayTime
|
||||
appSettings.featureFlags[skipUnknownPackWarningFlag] = !value.warnOnUnknownModpacks
|
||||
appSettings.featureFlags[skipNonEssentialWarningsFlag] = value.skipNonEssentialWarnings
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
async function saveBehaviorSettings(): Promise<void> {
|
||||
try {
|
||||
await save()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
settingsModal?.registerUnsavedChangesController({
|
||||
hasChanges: () => hasChanges.value,
|
||||
getOriginal: () => saved.value,
|
||||
getModified: () => changes.value,
|
||||
isSaving: () => saving.value,
|
||||
reset,
|
||||
save: saveBehaviorSettings,
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
settingsModal?.registerUnsavedChangesController(null)
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<section>
|
||||
<section class="border-0 border-b border-solid border-divider pb-6">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 id="sync-behavior-across-devices-label" class="m-0 text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(messages.syncAcrossDevicesTitle) }}
|
||||
</h2>
|
||||
<p class="m-0 mt-1 text-secondary">
|
||||
{{ formatMessage(messages.syncAcrossDevicesDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
<span
|
||||
v-tooltip="
|
||||
!auth.user.value ? formatMessage(messages.syncAcrossDevicesSignedOutTooltip) : undefined
|
||||
"
|
||||
class="inline-flex shrink-0"
|
||||
>
|
||||
<Toggle
|
||||
id="sync-behavior-across-devices"
|
||||
:model-value="Boolean(auth.user.value) && current.syncBehaviorAcrossDevices"
|
||||
:disabled="!auth.user.value"
|
||||
aria-labelledby="sync-behavior-across-devices-label"
|
||||
@update:model-value="current.syncBehaviorAcrossDevices = $event"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mt-6">
|
||||
<h2 class="m-0 text-xl font-semibold text-contrast">
|
||||
{{ formatMessage(messages.startupAndNavigationTitle) }}
|
||||
</h2>
|
||||
@@ -121,7 +266,7 @@ watch(
|
||||
{{ formatMessage(messages.minimizeLauncherDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle id="minimize-launcher" v-model="settings.hide_on_process_start" />
|
||||
<Toggle id="minimize-launcher" v-model="current.minimizeApp" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
@@ -131,16 +276,7 @@ watch(
|
||||
</h3>
|
||||
<p class="m-0 mt-1">{{ formatMessage(messages.toggleSidebarDescription) }}</p>
|
||||
</div>
|
||||
<Toggle
|
||||
id="toggle-sidebar"
|
||||
:model-value="settings.toggle_sidebar"
|
||||
@update:model-value="
|
||||
(e) => {
|
||||
settings.toggle_sidebar = !!e
|
||||
themeStore.toggleSidebar = settings.toggle_sidebar
|
||||
}
|
||||
"
|
||||
/>
|
||||
<Toggle id="toggle-sidebar" v-model="current.hideRightSidebar" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -159,17 +295,7 @@ watch(
|
||||
{{ formatMessage(messages.jumpBackIntoWorldsDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle
|
||||
id="jump-back-into-worlds"
|
||||
:model-value="themeStore.getFeatureFlag(worldsInHomeFlag)"
|
||||
@update:model-value="
|
||||
() => {
|
||||
const newValue = !themeStore.getFeatureFlag(worldsInHomeFlag)
|
||||
themeStore.featureFlags[worldsInHomeFlag] = newValue
|
||||
settings.feature_flags[worldsInHomeFlag] = newValue
|
||||
}
|
||||
"
|
||||
/>
|
||||
<Toggle id="jump-back-into-worlds" v-model="current.showJumpIn" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
@@ -179,17 +305,7 @@ watch(
|
||||
</h3>
|
||||
<p class="m-0 mt-1">{{ formatMessage(messages.showPlayTimeDescription) }}</p>
|
||||
</div>
|
||||
<Toggle
|
||||
id="show-play-time"
|
||||
:model-value="themeStore.getFeatureFlag(showPlayTimeFlag)"
|
||||
@update:model-value="
|
||||
() => {
|
||||
const newValue = !themeStore.getFeatureFlag(showPlayTimeFlag)
|
||||
themeStore.featureFlags[showPlayTimeFlag] = newValue
|
||||
settings.feature_flags[showPlayTimeFlag] = newValue
|
||||
}
|
||||
"
|
||||
/>
|
||||
<Toggle id="show-play-time" v-model="current.showPlayTime" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
@@ -199,16 +315,7 @@ watch(
|
||||
</h3>
|
||||
<p class="m-0 mt-1">{{ formatMessage(messages.hideNametagDescription) }}</p>
|
||||
</div>
|
||||
<Toggle
|
||||
id="hide-nametag-skins-page"
|
||||
:model-value="themeStore.hideNametagSkinsPage"
|
||||
@update:model-value="
|
||||
(e) => {
|
||||
themeStore.hideNametagSkinsPage = !!e
|
||||
settings.hide_nametag_skins_page = themeStore.hideNametagSkinsPage
|
||||
}
|
||||
"
|
||||
/>
|
||||
<Toggle id="hide-nametag-skins-page" v-model="current.hideNametag" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -229,15 +336,7 @@ watch(
|
||||
</div>
|
||||
<Toggle
|
||||
id="warn-before-installing-unknown-modpacks"
|
||||
:model-value="!themeStore.getFeatureFlag(skipUnknownPackWarningFlag)"
|
||||
@update:model-value="
|
||||
(e) => {
|
||||
const warnBeforeUnknownPackInstall = !!e
|
||||
const skipUnknownPackWarning = !warnBeforeUnknownPackInstall
|
||||
themeStore.featureFlags[skipUnknownPackWarningFlag] = skipUnknownPackWarning
|
||||
settings.feature_flags[skipUnknownPackWarningFlag] = skipUnknownPackWarning
|
||||
}
|
||||
"
|
||||
v-model="current.warnOnUnknownModpacks"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -250,17 +349,7 @@ watch(
|
||||
{{ formatMessage(messages.skipNonEssentialWarningsDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle
|
||||
id="skip-non-essential-warnings"
|
||||
:model-value="themeStore.getFeatureFlag(skipNonEssentialWarningsFlag)"
|
||||
@update:model-value="
|
||||
() => {
|
||||
const newValue = !themeStore.getFeatureFlag(skipNonEssentialWarningsFlag)
|
||||
themeStore.featureFlags[skipNonEssentialWarningsFlag] = newValue
|
||||
settings.feature_flags[skipNonEssentialWarningsFlag] = newValue
|
||||
}
|
||||
"
|
||||
/>
|
||||
<Toggle id="skip-non-essential-warnings" v-model="current.skipNonEssentialWarnings" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -2,17 +2,20 @@
|
||||
import { Button, Toggle } from '@modrinth/ui'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import {
|
||||
DEFAULT_FEATURE_FLAGS,
|
||||
type FeatureFlag,
|
||||
useAppSettings,
|
||||
} from '@/composables/use-app-settings.ts'
|
||||
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
||||
import { useTheming } from '@/store/state'
|
||||
import { DEFAULT_FEATURE_FLAGS, type FeatureFlag } from '@/store/theme.ts'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
|
||||
const settings = ref(await getSettings())
|
||||
const options = ref<FeatureFlag[]>(Object.keys(DEFAULT_FEATURE_FLAGS))
|
||||
const options = ref(Object.keys(DEFAULT_FEATURE_FLAGS) as FeatureFlag[])
|
||||
|
||||
function setFeatureFlag(key: string, value: boolean) {
|
||||
themeStore.featureFlags[key] = value
|
||||
function setFeatureFlag(key: FeatureFlag, value: boolean) {
|
||||
appSettings.featureFlags[key] = value
|
||||
settings.value.feature_flags[key] = value
|
||||
}
|
||||
|
||||
@@ -35,15 +38,15 @@ watch(
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
type="quiet"
|
||||
:disabled="themeStore.getFeatureFlag(option) === DEFAULT_FEATURE_FLAGS[option]"
|
||||
:disabled="appSettings.getFeatureFlag(option) === DEFAULT_FEATURE_FLAGS[option]"
|
||||
@click="setFeatureFlag(option, DEFAULT_FEATURE_FLAGS[option])"
|
||||
>
|
||||
Reset to default
|
||||
</Button>
|
||||
<Toggle
|
||||
id="advanced-rendering"
|
||||
:model-value="themeStore.getFeatureFlag(option)"
|
||||
@update:model-value="() => setFeatureFlag(option, !themeStore.getFeatureFlag(option))"
|
||||
:model-value="appSettings.getFeatureFlag(option)"
|
||||
@update:model-value="() => setFeatureFlag(option, !appSettings.getFeatureFlag(option))"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,74 +1,35 @@
|
||||
<template>
|
||||
<SharedLanguageSettings ref="languageSettings" product="app" :persist-locale="persistLocale" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Admonition,
|
||||
AutoLink,
|
||||
commonSettingsMessages,
|
||||
IntlFormatted,
|
||||
LanguageSelector,
|
||||
languageSelectorMessages,
|
||||
LOCALES,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { LanguageSettings as SharedLanguageSettings } from '@modrinth/ui'
|
||||
import { inject, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import i18n from '@/i18n.config'
|
||||
import { appSettingsModalContextKey } from '@/providers/app-settings-modal'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const settingsModal = inject(appSettingsModalContextKey, null)
|
||||
const languageSettings = ref<InstanceType<typeof SharedLanguageSettings> | null>(null)
|
||||
|
||||
const platform = computed(() => formatMessage(languageSelectorMessages.platformApp))
|
||||
onMounted(() => {
|
||||
settingsModal?.registerUnsavedChangesController({
|
||||
hasChanges: () => languageSettings.value?.hasChanges ?? false,
|
||||
getOriginal: () => languageSettings.value?.originalState ?? {},
|
||||
getModified: () => languageSettings.value?.modifiedState ?? {},
|
||||
isSaving: () => languageSettings.value?.saving ?? false,
|
||||
reset: () => languageSettings.value?.reset(),
|
||||
save: () => languageSettings.value?.save(),
|
||||
})
|
||||
})
|
||||
|
||||
const settings = ref(await get())
|
||||
onBeforeUnmount(() => {
|
||||
settingsModal?.registerUnsavedChangesController(null)
|
||||
})
|
||||
|
||||
watch(
|
||||
settings,
|
||||
async () => {
|
||||
await set(settings.value)
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
const $isChanging = ref(false)
|
||||
|
||||
async function onLocaleChange(newLocale: string) {
|
||||
if (settings.value.locale === newLocale) return
|
||||
|
||||
$isChanging.value = true
|
||||
try {
|
||||
i18n.global.locale.value = newLocale
|
||||
settings.value.locale = newLocale
|
||||
} finally {
|
||||
$isChanging.value = false
|
||||
}
|
||||
async function persistLocale(locale: string): Promise<void> {
|
||||
const settings = await get()
|
||||
if (settings.locale === locale) return
|
||||
await set({ ...settings, locale })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 class="m-0 text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(commonSettingsMessages.language) }}
|
||||
</h2>
|
||||
|
||||
<Admonition type="warning" class="mt-2 mb-4">
|
||||
{{ formatMessage(languageSelectorMessages.languageWarning, { platform }) }}
|
||||
</Admonition>
|
||||
|
||||
<p class="m-0 mb-4">
|
||||
<IntlFormatted
|
||||
:message-id="languageSelectorMessages.languagesDescription"
|
||||
:values="{ platform }"
|
||||
>
|
||||
<template #~crowdin-link="{ children }">
|
||||
<AutoLink to="https://translate.modrinth.com">
|
||||
<component :is="() => children" />
|
||||
</AutoLink>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
|
||||
<LanguageSelector
|
||||
:current-locale="settings.locale"
|
||||
:locales="LOCALES"
|
||||
:on-locale-change="onLocaleChange"
|
||||
:is-changing="$isChanging"
|
||||
/>
|
||||
</template>
|
||||
|
||||
+6
-6
@@ -14,14 +14,14 @@ import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { purge_cache_types } from '@/helpers/cache.js'
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import { showAppDbBackupsFolder } from '@/helpers/utils.js'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const settings = ref(await get())
|
||||
const purgeCacheConfirmModal = ref(null)
|
||||
const alwaysShowCopyDetailsFlag = 'always_show_copy_details'
|
||||
@@ -145,7 +145,7 @@ async function purgeCache() {
|
||||
}
|
||||
|
||||
function handlePurgeCacheClick() {
|
||||
if (themeStore.getFeatureFlag('skip_non_essential_warnings')) {
|
||||
if (appSettings.getFeatureFlag('skip_non_essential_warnings')) {
|
||||
void purgeCache()
|
||||
return
|
||||
}
|
||||
@@ -210,11 +210,11 @@ async function findLauncherDir() {
|
||||
</div>
|
||||
<Toggle
|
||||
id="always-show-copy-details"
|
||||
:model-value="themeStore.getFeatureFlag(alwaysShowCopyDetailsFlag)"
|
||||
:model-value="appSettings.getFeatureFlag(alwaysShowCopyDetailsFlag)"
|
||||
@update:model-value="
|
||||
() => {
|
||||
const newValue = !themeStore.getFeatureFlag(alwaysShowCopyDetailsFlag)
|
||||
themeStore.featureFlags[alwaysShowCopyDetailsFlag] = newValue
|
||||
const newValue = !appSettings.getFeatureFlag(alwaysShowCopyDetailsFlag)
|
||||
appSettings.featureFlags[alwaysShowCopyDetailsFlag] = newValue
|
||||
settings.feature_flags[alwaysShowCopyDetailsFlag] = newValue
|
||||
}
|
||||
"
|
||||
|
||||
Reference in New Issue
Block a user