mirror of
https://github.com/modrinth/code.git
synced 2026-08-26 09:34: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:
@@ -107,6 +107,7 @@ import type { Ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import {
|
||||
get_default_user,
|
||||
@@ -118,7 +119,6 @@ import {
|
||||
import { getPlayerHeadUrl } from '@/helpers/rendering/batch-skin-renderer.ts'
|
||||
import type { Skin } from '@/helpers/skins'
|
||||
import { get_available_skins } from '@/helpers/skins'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
@@ -20,11 +20,11 @@ import { computed, ref } from 'vue'
|
||||
|
||||
import { ChatIcon } from '@/assets/icons'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
||||
import { install_existing_instance } from '@/helpers/install'
|
||||
import { cancel_directory_change } from '@/helpers/settings.ts'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { install_existing_instance, install_pack_to_existing_instance } from '@/helpers/install'
|
||||
import { getInstanceIconUrl, kill, run } from '@/helpers/instance'
|
||||
import { get_by_instance_id } from '@/helpers/process'
|
||||
import { showInstanceInFolder } from '@/helpers/utils.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
@@ -39,17 +39,17 @@ import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { get as getSettings } from '@/helpers/settings.ts'
|
||||
import { getOS } from '@/helpers/utils.js'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
|
||||
const nativeDecorations = ref(true)
|
||||
const isMaximized = ref(false)
|
||||
const os = ref('')
|
||||
|
||||
const alwaysShowAppControls = computed(() => themeStore.getFeatureFlag('always_show_app_controls'))
|
||||
const alwaysShowAppControls = computed(() => appSettings.getFeatureFlag('always_show_app_controls'))
|
||||
|
||||
const showControls = computed(
|
||||
() =>
|
||||
|
||||
@@ -15,17 +15,17 @@ import { computed, ref } from 'vue'
|
||||
|
||||
import FriendsSection from '@/components/ui/friends/FriendsSection.vue'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { useFriends } from '@/composables/use-friends'
|
||||
import type { FriendWithUserData } from '@/helpers/friends.ts'
|
||||
import type { ModrinthCredentials } from '@/helpers/mr_auth'
|
||||
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
|
||||
const props = defineProps<{
|
||||
credentials: ModrinthCredentials | null
|
||||
@@ -39,11 +39,11 @@ type FriendsSectionCollapsedFlag =
|
||||
| 'friends_pending_collapsed'
|
||||
|
||||
function isFriendsSectionCollapsed(flag: FriendsSectionCollapsedFlag) {
|
||||
return themeStore.getFeatureFlag(flag)
|
||||
return appSettings.getFeatureFlag(flag)
|
||||
}
|
||||
|
||||
function setFriendsSectionCollapsed(flag: FriendsSectionCollapsedFlag, collapsed: boolean) {
|
||||
themeStore.featureFlags[flag] = collapsed
|
||||
appSettings.featureFlags[flag] = collapsed
|
||||
getSettings()
|
||||
.then((settings) => {
|
||||
settings.feature_flags[flag] = collapsed
|
||||
|
||||
@@ -13,11 +13,10 @@
|
||||
import { UnknownFileWarningModal } from '@modrinth/ui'
|
||||
import { ref, useTemplateRef } from 'vue'
|
||||
|
||||
import { type FeatureFlag, useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { get as getSettings, set as setSettings } from '@/helpers/settings'
|
||||
import { useTheming } from '@/store/state'
|
||||
import type { FeatureFlag } from '@/store/theme.ts'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const skipUnknownPackWarningFeatureFlag = 'skip_unknown_pack_warning' as FeatureFlag
|
||||
|
||||
const modal = useTemplateRef('modal')
|
||||
@@ -34,7 +33,7 @@ function show(
|
||||
fileName.value = selectedFileName
|
||||
externalFilesInModpack.value = selectedExternalFiles
|
||||
|
||||
if (themeStore.getFeatureFlag(skipUnknownPackWarningFeatureFlag)) {
|
||||
if (appSettings.getFeatureFlag(skipUnknownPackWarningFeatureFlag)) {
|
||||
void createInstance()
|
||||
return
|
||||
}
|
||||
@@ -50,7 +49,7 @@ function reset() {
|
||||
|
||||
async function proceed(dontShowAgain: boolean) {
|
||||
if (dontShowAgain) {
|
||||
themeStore.featureFlags[skipUnknownPackWarningFeatureFlag] = true
|
||||
appSettings.featureFlags[skipUnknownPackWarningFeatureFlag] = true
|
||||
const settings = await getSettings()
|
||||
settings.feature_flags[skipUnknownPackWarningFeatureFlag] = true
|
||||
await setSettings(settings)
|
||||
|
||||
@@ -9,13 +9,13 @@ import { useRouter } from 'vue-router'
|
||||
import InstanceCardView from '@/components/ui/library/instance-group/instance-card-view.vue'
|
||||
import { getLibraryInstanceSelectionKey, useLibrary } from '@/components/ui/library/use-library'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { install_existing_instance, install_pack_to_existing_instance } from '@/helpers/install'
|
||||
import { kill, run } from '@/helpers/instance'
|
||||
import { get_by_instance_id } from '@/helpers/process'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { showInstanceInFolder } from '@/helpers/utils.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
type ProcessEvent = 'installing' | 'launched' | 'finished'
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
name: { id: 'app.library.sort.name', defaultMessage: 'Name' },
|
||||
loaderSort: { id: 'app.library.sort.loader', defaultMessage: 'Loader' },
|
||||
gameVersionSort: { id: 'app.library.sort.game-version', defaultMessage: 'Game version' },
|
||||
lastPlayed: { id: 'app.library.sort.last-played', defaultMessage: 'Last played' },
|
||||
hoursPlayed: { id: 'app.library.sort.hours-played', defaultMessage: 'Hours played' },
|
||||
dateCreated: { id: 'app.library.sort.date-created', defaultMessage: 'Date created' },
|
||||
@@ -34,6 +36,8 @@ const sortLabels = {
|
||||
'Hours played': messages.hoursPlayed,
|
||||
'Date created': messages.dateCreated,
|
||||
'Date modified': messages.dateModified,
|
||||
Loader: messages.loaderSort,
|
||||
'Game version': messages.gameVersionSort,
|
||||
}
|
||||
|
||||
const groupLabels = {
|
||||
@@ -60,6 +64,7 @@ const groupOptions: ComboboxOption<LibraryGroupBy>[] = libraryGroupOptions.map((
|
||||
class="w-max"
|
||||
:options="sortOptions"
|
||||
:show-icon-in-selected="false"
|
||||
:max-height="320"
|
||||
dropdown-min-width="160px"
|
||||
>
|
||||
<template #prefix>
|
||||
|
||||
@@ -36,6 +36,8 @@ export const librarySortOptions = [
|
||||
'Hours played',
|
||||
'Date created',
|
||||
'Date modified',
|
||||
'Loader',
|
||||
'Game version',
|
||||
] as const
|
||||
|
||||
export const libraryGroupOptions = [
|
||||
@@ -328,6 +330,18 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
case 'Name':
|
||||
visibleInstances.sort((a, b) => a.name.localeCompare(b.name))
|
||||
break
|
||||
case 'Loader':
|
||||
visibleInstances.sort((a, b) =>
|
||||
formatLoader(formatMessage, a.loader).localeCompare(
|
||||
formatLoader(formatMessage, b.loader),
|
||||
),
|
||||
)
|
||||
break
|
||||
case 'Game version':
|
||||
visibleInstances.sort((a, b) =>
|
||||
a.game_version.localeCompare(b.game_version, undefined, { numeric: true }),
|
||||
)
|
||||
break
|
||||
case 'Last played':
|
||||
visibleInstances.sort((a, b) => dayjs(b.last_played ?? 0).diff(dayjs(a.last_played ?? 0)))
|
||||
break
|
||||
|
||||
+1
-1
@@ -10,8 +10,8 @@ import {
|
||||
import { Admonition, Button, ButtonLink, Collapsible, IconButton, NewModal } from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
import { findMinecraftAuthError, type MinecraftAuthError } from './minecraft-auth-errors'
|
||||
|
||||
|
||||
+1
-1
@@ -63,9 +63,9 @@ import { inject, type Ref, ref } from 'vue'
|
||||
|
||||
import steveImage from '@/assets/steve-look-up-left.webp'
|
||||
import type AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { login as loginFlow, set_default_user } from '@/helpers/auth.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const accountsCard = inject('accountsCard') as Ref<InstanceType<typeof AccountsCard> | null>
|
||||
|
||||
@@ -36,16 +36,16 @@ import LanguageSettings from '@/components/ui/settings/display/LanguageSettings.
|
||||
import DefaultInstanceSettings from '@/components/ui/settings/instances/DefaultInstanceSettings.vue'
|
||||
import JavaSettings from '@/components/ui/settings/instances/JavaSettings.vue'
|
||||
import ResourceManagementSettings from '@/components/ui/settings/instances/ResourceManagementSettings.vue'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import {
|
||||
appSettingsModalContextKey,
|
||||
type UnsavedChangesController,
|
||||
} from '@/providers/app-settings-modal'
|
||||
import { injectAppUpdateDownloadProgress } from '@/providers/download-progress.ts'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
// TODO: Apply COMPONENT_STRUCTURE.md here and extract out common setting option components
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
@@ -157,7 +157,9 @@ const tabs = [
|
||||
},
|
||||
]
|
||||
|
||||
const availableTabs = computed(() => tabs.filter((tab) => !tab.developerOnly || themeStore.devMode))
|
||||
const availableTabs = computed(() =>
|
||||
tabs.filter((tab) => !tab.developerOnly || appSettings.devMode),
|
||||
)
|
||||
|
||||
const modal = ref<InstanceType<typeof TabbedModal> | null>(null)
|
||||
const unsavedChangesPopup = ref<{ nudge: () => void } | null>(null)
|
||||
@@ -170,10 +172,19 @@ const modifiedUnsavedChangesState = computed(
|
||||
() => unsavedChangesController.value?.getModified() ?? emptyUnsavedChangesState,
|
||||
)
|
||||
const savingUnsavedChanges = computed(() => unsavedChangesController.value?.isSaving() ?? false)
|
||||
const hasUnsavedChanges = computed(() => unsavedChangesController.value?.hasChanges() ?? false)
|
||||
const hasUnsavedChanges = computed(
|
||||
() =>
|
||||
(unsavedChangesController.value?.hasChanges() ?? false) ||
|
||||
(unsavedChangesController.value?.isSaving() ?? false),
|
||||
)
|
||||
|
||||
function canLeaveCurrentTab(): boolean {
|
||||
if (!unsavedChangesController.value?.hasChanges()) return true
|
||||
if (
|
||||
!unsavedChangesController.value?.hasChanges() &&
|
||||
!unsavedChangesController.value?.isSaving()
|
||||
) {
|
||||
return true
|
||||
}
|
||||
unsavedChangesPopup.value?.nudge()
|
||||
return false
|
||||
}
|
||||
@@ -233,8 +244,8 @@ function devModeCount() {
|
||||
if (devModeCounter.value > 5) {
|
||||
const selectedTab = modal.value ? availableTabs.value[modal.value.selectedTab] : undefined
|
||||
|
||||
themeStore.devMode = !themeStore.devMode
|
||||
settings.value.developer_mode = !!themeStore.devMode
|
||||
appSettings.devMode = !appSettings.devMode
|
||||
settings.value.developer_mode = !!appSettings.devMode
|
||||
devModeCounter.value = 0
|
||||
|
||||
if (modal.value) {
|
||||
@@ -298,7 +309,7 @@ const messages = defineMessages({
|
||||
<ProgressBar :progress="progress" />
|
||||
</template>
|
||||
</div>
|
||||
<p v-if="themeStore.devMode" class="text-brand font-semibold m-0 mb-2">
|
||||
<p v-if="appSettings.devMode" class="text-brand font-semibold m-0 mb-2">
|
||||
{{ formatMessage(developerModeEnabled) }}
|
||||
</p>
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -306,8 +317,8 @@ const messages = defineMessages({
|
||||
:aria-label="formatMessage(messages.developerModeButtonLabel)"
|
||||
class="p-0 m-0 bg-transparent border-none cursor-pointer button-animation"
|
||||
:class="{
|
||||
'text-brand': themeStore.devMode,
|
||||
'text-secondary': !themeStore.devMode,
|
||||
'text-brand': appSettings.devMode,
|
||||
'text-secondary': !appSettings.devMode,
|
||||
}"
|
||||
@click="devModeCount"
|
||||
>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
"
|
||||
|
||||
+3
-3
@@ -9,6 +9,7 @@ import { useQueryClient } from '@tanstack/vue-query'
|
||||
import { type Ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { get_user } from '@/helpers/cache'
|
||||
import { toError } from '@/helpers/errors'
|
||||
import {
|
||||
@@ -18,7 +19,6 @@ import {
|
||||
} from '@/helpers/install'
|
||||
import { list } from '@/helpers/instance'
|
||||
import { useSharedInstanceErrors } from '@/helpers/shared-instance-errors'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
import { parseSharedInstanceInviteNotification } from './shared-instance-invite-parser'
|
||||
import type { AppNotification, SharedInstanceInvite } from './shared-instance-invite-types'
|
||||
@@ -58,7 +58,7 @@ export function useSharedInstanceInviteHandler(
|
||||
const popupNotificationManager = injectPopupNotificationManager()
|
||||
const queryClient = useQueryClient()
|
||||
const router = useRouter()
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const displayedNotifications = new Set<string | number>()
|
||||
const displayedNotificationKeys = new Set<string>()
|
||||
const popupNotificationIds = new Set<string | number>()
|
||||
@@ -121,7 +121,7 @@ export function useSharedInstanceInviteHandler(
|
||||
(instance) => instance.shared_instance?.id === sharedInstanceId,
|
||||
)
|
||||
|
||||
if (!existingInstance || themeStore.getFeatureFlag('skip_non_essential_warnings')) {
|
||||
if (!existingInstance || appSettings.getFeatureFlag('skip_non_essential_warnings')) {
|
||||
showInstall(preview, install, creator)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -28,12 +28,12 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { getInstanceIconUrl, kill, run } from '@/helpers/instance'
|
||||
import { get_by_instance_id } from '@/helpers/process'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { showInstanceInFolder } from '@/helpers/utils'
|
||||
import { handleSevereError } from '@/store/error'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
@@ -10,6 +10,8 @@ import { computed, onMounted, ref, watch } from 'vue'
|
||||
import InstanceItem from '@/components/ui/world/InstanceItem.vue'
|
||||
import WorldItem from '@/components/ui/world/WorldItem.vue'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { kill, run } from '@/helpers/instance'
|
||||
import { get_all } from '@/helpers/process'
|
||||
@@ -29,8 +31,6 @@ import {
|
||||
start_join_singleplayer_world,
|
||||
type WorldWithInstance,
|
||||
} from '@/helpers/worlds.ts'
|
||||
import { handleSevereError } from '@/store/error'
|
||||
import { useTheming } from '@/store/theme.ts'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
@@ -42,7 +42,7 @@ const props = defineProps<{
|
||||
recentInstances: GameInstance[]
|
||||
}>()
|
||||
|
||||
const theme = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
|
||||
const jumpBackInItems = ref<JumpBackInItem[]>([])
|
||||
const loading = ref(true)
|
||||
@@ -76,7 +76,7 @@ type WorldJumpBackInItem = BaseJumpBackInItem & {
|
||||
|
||||
type JumpBackInItem = InstanceJumpBackInItem | WorldJumpBackInItem
|
||||
|
||||
const showWorlds = computed(() => theme.getFeatureFlag('worlds_in_home'))
|
||||
const showWorlds = computed(() => appSettings.getFeatureFlag('worlds_in_home'))
|
||||
|
||||
watch([() => props.recentInstances, () => showWorlds.value], async () => {
|
||||
await populateJumpBackIn().catch(() => {
|
||||
|
||||
Reference in New Issue
Block a user