mirror of
https://github.com/modrinth/code.git
synced 2026-08-27 01:54:47 +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:
@@ -10,6 +10,7 @@ import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Router } from 'vue-router'
|
||||
|
||||
import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import {
|
||||
install_job_dismiss,
|
||||
install_job_list,
|
||||
@@ -23,7 +24,6 @@ import {
|
||||
} from '@/helpers/install'
|
||||
import { get_many as getInstances } from '@/helpers/instance'
|
||||
import { injectAppEvents } from '@/providers/app-events'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
const messages = defineMessages({
|
||||
installs: {
|
||||
@@ -236,7 +236,7 @@ export async function useInstallJobNotifications(opts: {
|
||||
}) {
|
||||
const appEvents = injectAppEvents()
|
||||
const { formatMessage } = useVIntl()
|
||||
const themeStore = useTheming()
|
||||
const appSettings = useAppSettings()
|
||||
const jobs = ref<InstallJobSnapshot[]>([])
|
||||
const iconUrls = ref<Record<string, string | null>>({})
|
||||
const instanceNames = ref<Record<string, string>>({})
|
||||
@@ -421,7 +421,7 @@ export async function useInstallJobNotifications(opts: {
|
||||
}
|
||||
|
||||
function shouldShowCopyDetails(job: InstallJobSnapshot): boolean {
|
||||
return isTerminalJob(job) || themeStore.getFeatureFlag('always_show_copy_details')
|
||||
return isTerminalJob(job) || appSettings.getFeatureFlag('always_show_copy_details')
|
||||
}
|
||||
|
||||
function isCopied(job: InstallJobSnapshot): boolean {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { reactive, ref } from 'vue'
|
||||
|
||||
export const DEFAULT_FEATURE_FLAGS = {
|
||||
project_background: false,
|
||||
page_path: false,
|
||||
worlds_in_home: true,
|
||||
server_project_qa: false,
|
||||
show_version_environment_column: false,
|
||||
server_ram_as_bytes_always_on: false,
|
||||
always_show_app_controls: false,
|
||||
skip_non_essential_warnings: false,
|
||||
skip_unknown_pack_warning: false,
|
||||
pride_fundraiser: true,
|
||||
i18n_debug: false,
|
||||
show_instance_play_time: true,
|
||||
advanced_filters_collapsed: true,
|
||||
always_show_copy_details: false,
|
||||
hide_installed_modpacks: false,
|
||||
friends_active_collapsed: false,
|
||||
friends_online_collapsed: false,
|
||||
friends_offline_collapsed: true,
|
||||
friends_pending_collapsed: true,
|
||||
dismissed_photosensitivity_filter_warning: false,
|
||||
}
|
||||
|
||||
export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS
|
||||
type FeatureFlags = Record<FeatureFlag, boolean>
|
||||
|
||||
const syncBehaviorAcrossDevices = ref(false)
|
||||
const featureFlags = reactive<FeatureFlags>({ ...DEFAULT_FEATURE_FLAGS })
|
||||
|
||||
function setBehaviorSyncAcrossDevices(enabled: boolean): void {
|
||||
syncBehaviorAcrossDevices.value = enabled
|
||||
}
|
||||
|
||||
function getFeatureFlag(key: FeatureFlag): boolean {
|
||||
return featureFlags[key] ?? DEFAULT_FEATURE_FLAGS[key]
|
||||
}
|
||||
|
||||
const appSettings = reactive({
|
||||
syncBehaviorAcrossDevices,
|
||||
hideNametagSkinsPage: false,
|
||||
toggleSidebar: false,
|
||||
devMode: false,
|
||||
featureFlags,
|
||||
setBehaviorSyncAcrossDevices,
|
||||
getFeatureFlag,
|
||||
})
|
||||
|
||||
export function useAppSettings() {
|
||||
return appSettings
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
import { findMinecraftAuthError } from '@/components/ui/minecraft-auth-error-modal/minecraft-auth-errors'
|
||||
|
||||
const errorState = reactive({
|
||||
errorModal: null,
|
||||
minecraftAuthErrorModal: null,
|
||||
minecraftRequiredModal: null,
|
||||
setErrorModal(ref) {
|
||||
this.errorModal = ref
|
||||
},
|
||||
setMinecraftAuthErrorModal(ref) {
|
||||
this.minecraftAuthErrorModal = ref
|
||||
},
|
||||
setMinecraftRequiredModal(ref) {
|
||||
this.minecraftRequiredModal = ref
|
||||
},
|
||||
showError(error, context, closable = true, source = null) {
|
||||
const errorMessage = error.message?.toLowerCase()
|
||||
if (
|
||||
(errorMessage?.includes('user is not logged in') ||
|
||||
errorMessage?.includes('cannot play instance since minecraft is required')) &&
|
||||
this.minecraftRequiredModal
|
||||
) {
|
||||
this.minecraftRequiredModal.show()
|
||||
return
|
||||
}
|
||||
if (
|
||||
error.message &&
|
||||
(error.message.includes('Minecraft authentication error:') ||
|
||||
findMinecraftAuthError(error.message)) &&
|
||||
this.minecraftAuthErrorModal
|
||||
) {
|
||||
this.minecraftAuthErrorModal.show(error)
|
||||
return
|
||||
}
|
||||
this.errorModal.show(error, context, closable, source)
|
||||
},
|
||||
})
|
||||
|
||||
export function useError() {
|
||||
return errorState
|
||||
}
|
||||
|
||||
export const handleSevereError = (error, context) => {
|
||||
useError().showError(error, context)
|
||||
console.error(error)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
|
||||
export const THEME_OPTIONS = ['dark', 'light', 'oled', 'retro', 'system'] as const
|
||||
|
||||
export type ColorTheme = (typeof THEME_OPTIONS)[number]
|
||||
type Theme = Exclude<ColorTheme, 'system'>
|
||||
|
||||
const preferred = ref<ColorTheme>('dark')
|
||||
const preview = ref<ColorTheme | null>(null)
|
||||
const advancedRendering = ref(true)
|
||||
const syncAcrossDevices = ref(false)
|
||||
const nativeThemeQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
const native = ref<Theme>(nativeThemeQuery.matches ? 'dark' : 'light')
|
||||
const active = computed<Theme>(() => {
|
||||
const selectedTheme = preview.value ?? preferred.value
|
||||
return selectedTheme === 'system' ? native.value : selectedTheme
|
||||
})
|
||||
|
||||
nativeThemeQuery.addEventListener('change', (event) => {
|
||||
native.value = event.matches ? 'dark' : 'light'
|
||||
})
|
||||
|
||||
watch(
|
||||
active,
|
||||
(theme) => {
|
||||
const html = document.documentElement
|
||||
for (const option of THEME_OPTIONS) {
|
||||
html.classList.remove(`${option}-mode`)
|
||||
}
|
||||
html.classList.add(`${theme}-mode`)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const theme = reactive({
|
||||
preferred,
|
||||
preview,
|
||||
active,
|
||||
native,
|
||||
syncAcrossDevices,
|
||||
advancedRendering,
|
||||
options: THEME_OPTIONS,
|
||||
})
|
||||
|
||||
export function useTheme() {
|
||||
return theme
|
||||
}
|
||||
Reference in New Issue
Block a user