mirror of
https://github.com/modrinth/code.git
synced 2026-09-02 13:05:50 +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,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>
|
||||
|
||||
Reference in New Issue
Block a user