import { computed, ref, watch } from 'vue'; import { defineStore } from 'pinia'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; import { APP_CJK_FONT_PACK_DEFAULT_KEY, APP_CJK_FONT_PACKS, APP_FONT_DEFAULT_KEY, APP_FONT_FAMILIES, SEARCH_LIMIT_MAX, SEARCH_LIMIT_MIN, TABLE_MAX_SIZE_MAX, TABLE_MAX_SIZE_MIN, THEME_CONFIG } from '../../shared/constants'; import { applyAppCjkFontPack, HueToHex, applyAppFontFamily, changeAppThemeStyle, changeHtmlLangAttribute, getThemeMode, updateTrustColorClasses } from '../../shared/utils/base/ui'; import { computeTrustLevel, getNameColour } from '../../shared/utils'; import { database } from '../../services/database'; import { loadLocalizedStrings } from '../../plugins'; import { useFeedStore } from '../feed'; import { useGameLogStore } from '../gameLog'; import { useUiStore } from '../ui'; import { useUserStore } from '../user'; import { useVrStore } from '../vr'; import { useVrcxStore } from '../vrcx'; import { watchState } from '../../services/watchState'; import configRepository from '../../services/config'; export const useAppearanceSettingsStore = defineStore( 'AppearanceSettings', () => { const vrStore = useVrStore(); const feedStore = useFeedStore(); const gameLogStore = useGameLogStore(); const vrcxStore = useVrcxStore(); const userStore = useUserStore(); const router = useRouter(); const uiStore = useUiStore(); const { locale } = useI18n(); const MAX_TABLE_PAGE_SIZE = 1000; const DEFAULT_TABLE_PAGE_SIZES = [10, 15, 20, 25, 50, 100]; const appLanguage = ref('en'); const themeMode = ref(''); const isDarkMode = ref(false); const lastDarkTheme = ref('dark'); const appFontFamily = ref('inter'); const customFontFamily = ref(''); const appCjkFontPack = ref(APP_CJK_FONT_PACK_DEFAULT_KEY); const displayVRCPlusIconsAsAvatar = ref(false); const hideNicknames = ref(false); const showInstanceIdInLocation = ref(false); const isAgeGatedInstancesVisible = ref(false); const sortFavorites = ref(true); const instanceUsersSortAlphabetical = ref(false); const tablePageSize = ref(15); const tablePageSizes = ref([...DEFAULT_TABLE_PAGE_SIZES]); const dtHour12 = ref(false); const dtIsoFormat = ref(false); const weekStartsOn = ref(1); const sidebarSortMethod1 = ref('Sort Private to Bottom'); const sidebarSortMethod2 = ref('Sort by Time in Instance'); const sidebarSortMethod3 = ref('Sort by Last Active'); const sidebarSortMethods = ref([ 'Sort Private to Bottom', 'Sort by Time in Instance', 'Sort by Last Active' ]); const navWidth = ref(240); const isSidebarGroupByInstance = ref(true); const isHideFriendsInSameInstance = ref(false); const isSameInstanceAboveFavorites = ref(false); const isSidebarDivideByFriendGroup = ref(false); const sidebarFavoriteGroups = ref([]); const sidebarFavoriteGroupOrder = ref([]); const hideUserNotes = ref(false); const hideUserMemos = ref(false); const hideUnfriends = ref(false); const randomUserColours = ref(false); const tableDensity = ref('standard'); const TRUST_COLOR_DEFAULTS = Object.freeze({ untrusted: '#CCCCCC', basic: '#1778FF', known: '#2BCF5C', trusted: '#FF7B42', veteran: '#B18FFF', vip: '#FF2626', troll: '#782F2F' }); const trustColor = ref({ ...TRUST_COLOR_DEFAULTS }); const currentCulture = ref(''); const notificationIconDot = ref(false); const isNavCollapsed = ref(true); const isSideBarTabShow = computed(() => { const currentRouteName = router.currentRoute.value?.name; return ![ 'friends-locations', 'friend-list', 'charts-instance', 'charts-mutual', 'charts-hot-worlds' ].includes(currentRouteName); }); const isDataTableStriped = ref(false); const showPointerOnHover = ref(false); const accessibleStatusIndicators = ref(false); const useOfficialStatusColors = ref(true); const showNewDashboardButton = ref(true); const tableLimitsDialog = ref({ visible: false, maxTableSize: 500, searchLimit: 5000 }); const clampInt = (value, min, max) => { const n = parseInt(value, 10); return Math.min(max, Math.max(min, n)); }; const resolveLastDarkTheme = (value, fallback = 'dark') => { const normalized = String(value || '').trim(); return THEME_CONFIG[normalized]?.isDark === true ? normalized : fallback; }; /** * */ async function initAppearanceSettings() { const { initThemeMode, isDarkMode: initDarkMode } = await getThemeMode(configRepository); const fallbackDarkTheme = THEME_CONFIG[initThemeMode]?.isDark === true ? initThemeMode : 'dark'; const [ appLanguageConfig, displayVRCPlusIconsAsAvatarConfig, hideNicknamesConfig, showInstanceIdInLocationConfig, isAgeGatedInstancesVisibleConfig, sortFavoritesConfig, instanceUsersSortAlphabeticalConfig, tablePageSizeConfig, tablePageSizesConfig, dtHour12Config, dtIsoFormatConfig, weekStartsOnConfig, sidebarSortMethodsConfig, navWidthConfig, isSidebarGroupByInstanceConfig, isHideFriendsInSameInstanceConfig, isSameInstanceAboveFavoritesConfig, isSidebarDivideByFriendGroupConfig, sidebarFavoriteGroupsConfig, sidebarFavoriteGroupOrderConfig, hideUserNotesConfig, hideUserMemosConfig, hideUnfriendsConfig, randomUserColoursConfig, tableDensityConfig, compactTableModeConfig, trustColorConfig, notificationIconDotConfig, navIsCollapsedConfig, dataTableStripedConfig, showPointerOnHoverConfig, accessibleStatusIndicatorsConfig, useOfficialStatusColorsConfig, showNewDashboardButtonConfig, appFontFamilyConfig, customFontFamilyConfig, appCjkFontPackConfig, lastDarkThemeConfig ] = await Promise.all([ configRepository.getString('VRCX_appLanguage'), configRepository.getBool('displayVRCPlusIconsAsAvatar', true), configRepository.getBool('VRCX_hideNicknames', false), configRepository.getBool( 'VRCX_showInstanceIdInLocation', false ), configRepository.getBool( 'VRCX_isAgeGatedInstancesVisible', true ), configRepository.getBool('VRCX_sortFavorites', true), configRepository.getBool( 'VRCX_instanceUsersSortAlphabetical', false ), configRepository.getInt('VRCX_tablePageSize', 20), configRepository.getString( 'VRCX_tablePageSizes', JSON.stringify(DEFAULT_TABLE_PAGE_SIZES) ), configRepository.getBool('VRCX_dtHour12', false), configRepository.getBool('VRCX_dtIsoFormat', false), configRepository.getInt('VRCX_weekStartsOn', 1), configRepository.getString( 'VRCX_sidebarSortMethods', JSON.stringify([ 'Sort Private to Bottom', 'Sort by Time in Instance', 'Sort by Last Active' ]) ), configRepository.getInt('VRCX_navPanelWidth', 240), configRepository.getBool('VRCX_sidebarGroupByInstance', true), configRepository.getBool( 'VRCX_hideFriendsInSameInstance', false ), configRepository.getBool( 'VRCX_sameInstanceAboveFavorites', false ), configRepository.getBool( 'VRCX_sidebarDivideByFriendGroup', true ), configRepository.getString('VRCX_sidebarFavoriteGroups', '[]'), configRepository.getString( 'VRCX_sidebarFavoriteGroupOrder', '[]' ), configRepository.getBool('VRCX_hideUserNotes', false), configRepository.getBool('VRCX_hideUserMemos', false), configRepository.getBool('VRCX_hideUnfriends', false), configRepository.getBool('VRCX_randomUserColours', false), configRepository.getString('VRCX_tableDensity'), configRepository.getBool('VRCX_compactTableMode', false), configRepository.getString( 'VRCX_trustColor', JSON.stringify(TRUST_COLOR_DEFAULTS) ), configRepository.getBool('VRCX_notificationIconDot', true), configRepository.getBool('VRCX_navIsCollapsed', false), configRepository.getBool('VRCX_dataTableStriped', false), configRepository.getBool('VRCX_showPointerOnHover', false), configRepository.getBool( 'VRCX_accessibleStatusIndicators', false ), configRepository.getBool('VRCX_useOfficialStatusColors', true), configRepository.getBool('VRCX_showNewDashboardButton', true), configRepository.getString( 'VRCX_fontFamily', APP_FONT_DEFAULT_KEY ), configRepository.getString('VRCX_customFontFamily', ''), configRepository.getString( 'VRCX_cjkFontPack', APP_CJK_FONT_PACK_DEFAULT_KEY ), configRepository.getString( 'VRCX_lastDarkTheme', fallbackDarkTheme ) ]); if (appLanguageConfig) { await changeAppLanguage(appLanguageConfig); } else { // First launch: load en in-memory only, do NOT persist. // Login.vue detectAndPromptLanguage() will handle first-time language selection. await loadLocalizedStrings('en'); appLanguage.value = 'en'; locale.value = 'en'; changeHtmlLangAttribute('en'); } themeMode.value = initThemeMode; isDarkMode.value = initDarkMode; lastDarkTheme.value = resolveLastDarkTheme( lastDarkThemeConfig, fallbackDarkTheme ); const normalizedAppFontFamily = normalizeAppFontFamily(appFontFamilyConfig); appFontFamily.value = normalizedAppFontFamily; customFontFamily.value = customFontFamilyConfig || ''; appCjkFontPack.value = normalizeAppCjkFontPack(appCjkFontPackConfig); applyAppFontFamily(appFontFamily.value, customFontFamily.value); applyAppCjkFontPack(appCjkFontPack.value); if (normalizedAppFontFamily !== appFontFamilyConfig) { configRepository.setString( 'VRCX_fontFamily', normalizedAppFontFamily ); } displayVRCPlusIconsAsAvatar.value = displayVRCPlusIconsAsAvatarConfig; hideNicknames.value = hideNicknamesConfig; showInstanceIdInLocation.value = showInstanceIdInLocationConfig; isAgeGatedInstancesVisible.value = isAgeGatedInstancesVisibleConfig; sortFavorites.value = sortFavoritesConfig; instanceUsersSortAlphabetical.value = instanceUsersSortAlphabeticalConfig; tablePageSizes.value = normalizeTablePageSizes( JSON.parse(tablePageSizesConfig) ); setTablePageSize(tablePageSizeConfig); dtHour12.value = dtHour12Config; dtIsoFormat.value = dtIsoFormatConfig; weekStartsOn.value = [0, 1, 6].includes(weekStartsOnConfig) ? weekStartsOnConfig : 1; currentCulture.value = await AppApi.CurrentCulture(); sidebarSortMethods.value = JSON.parse(sidebarSortMethodsConfig); if (sidebarSortMethods.value?.length === 3) { sidebarSortMethod1.value = sidebarSortMethods.value[0]; sidebarSortMethod2.value = sidebarSortMethods.value[1]; sidebarSortMethod3.value = sidebarSortMethods.value[2]; } if (trustColorConfig !== JSON.stringify(TRUST_COLOR_DEFAULTS)) { await configRepository.setString( 'VRCX_trustColor', JSON.stringify(TRUST_COLOR_DEFAULTS) ); } trustColor.value = { ...TRUST_COLOR_DEFAULTS }; navWidth.value = clampInt(navWidthConfig, 64, 480); isSidebarGroupByInstance.value = isSidebarGroupByInstanceConfig; isHideFriendsInSameInstance.value = isHideFriendsInSameInstanceConfig; isSameInstanceAboveFavorites.value = isSameInstanceAboveFavoritesConfig; isSidebarDivideByFriendGroup.value = isSidebarDivideByFriendGroupConfig; sidebarFavoriteGroups.value = JSON.parse( sidebarFavoriteGroupsConfig ); sidebarFavoriteGroupOrder.value = JSON.parse( sidebarFavoriteGroupOrderConfig ); hideUserNotes.value = hideUserNotesConfig; hideUserMemos.value = hideUserMemosConfig; hideUnfriends.value = hideUnfriendsConfig; randomUserColours.value = randomUserColoursConfig; notificationIconDot.value = notificationIconDotConfig; const resolvedTableDensity = normalizeTableDensity( tableDensityConfig || (compactTableModeConfig ? 'compact' : 'standard') ); tableDensity.value = resolvedTableDensity; applyTableDensity(tableDensity.value); if (!tableDensityConfig) { configRepository.setString( 'VRCX_tableDensity', tableDensity.value ); } isNavCollapsed.value = navIsCollapsedConfig; isDataTableStriped.value = dataTableStripedConfig; showPointerOnHover.value = showPointerOnHoverConfig; accessibleStatusIndicators.value = accessibleStatusIndicatorsConfig; useOfficialStatusColors.value = useOfficialStatusColorsConfig; showNewDashboardButton.value = showNewDashboardButtonConfig; applyPointerHoverClass(); applyAccessibleStatusClass(); applyOfficialStatusColorsClass(); await configRepository.remove('VRCX_navWidth'); // Migrate old settings // Assume all exist if one does await mergeOldSortMethodsSettings(); updateTrustColorClasses(trustColor.value); vrStore.updateVRConfigVars(); } initAppearanceSettings(); watch( () => watchState.isFriendsLoaded, (isFriendsLoaded) => { if (isFriendsLoaded) { tryInitUserColours(); } }, { flush: 'sync' } ); /** * * @param {string} language */ async function changeAppLanguage(language) { await setAppLanguage(language); vrStore.updateVRConfigVars(); } /** * @param {string} language */ async function setAppLanguage(language) { console.log('Language changed:', language); await loadLocalizedStrings(language); appLanguage.value = language; configRepository.setString('VRCX_appLanguage', language); locale.value = appLanguage.value; changeHtmlLangAttribute(language); } /** * * @param {string} field * @param {string} color * @param {boolean} setRandomColor * @returns {Promise} */ async function updateTrustColor(field, color, setRandomColor = false) { if (setRandomColor) { setRandomUserColours(); } if (typeof userStore.currentUser?.id === 'undefined') { return; } if (field && color) { setTrustColor({ ...trustColor.value, [field]: color }); } if (randomUserColours.value) { const colour = await getNameColour( userStore.currentUser.id, isDarkMode.value ); userStore.setCurrentUserColour(colour); userColourInit(); } else { applyUserTrustLevel(userStore.currentUser); userStore.cachedUsers.forEach((ref) => { applyUserTrustLevel(ref); }); } updateTrustColorClasses(trustColor.value); } /** * * @param customFunc */ async function userColourInit(customFunc) { let dictObject = null; if (typeof customFunc === 'function') { dictObject = customFunc(userStore.cachedUsers.keys()); } else { dictObject = await AppApi.GetColourBulk( Array.from(userStore.cachedUsers.keys()) ); } if (!dictObject) { console.warn('No user colour data found'); return; } if (LINUX) { // @ts-ignore dictObject = Object.fromEntries(dictObject); } for (const [userId, hue] of Object.entries(dictObject)) { const ref = userStore.cachedUsers.get(userId); if (typeof ref !== 'undefined') { ref.$userColour = HueToHex(hue, isDarkMode.value); } } } /** * * @param {object} ref */ function applyUserTrustLevel(ref) { const trust = computeTrustLevel(ref.tags, ref.developerType); ref.$isModerator = trust.isModerator; ref.$isTroll = trust.isTroll; ref.$isProbableTroll = trust.isProbableTroll; ref.$trustLevel = trust.trustLevel; ref.$trustClass = trust.trustClass; ref.$trustSortNum = trust.trustSortNum; if (randomUserColours.value && watchState.isFriendsLoaded) { if (!ref.$userColour) { getNameColour(ref.id, isDarkMode.value).then((colour) => { ref.$userColour = colour; }); } } else { ref.$userColour = trustColor.value[trust.trustColorKey]; } } window .matchMedia('(prefers-color-scheme: dark)') .addEventListener('change', async () => { if (themeMode.value === 'system') { setThemeMode(themeMode.value); } }); /** * @param {string} mode */ function setThemeMode(mode) { themeMode.value = mode; configRepository.setString('VRCX_ThemeMode', mode); if (THEME_CONFIG[mode]?.isDark === true) { const normalized = resolveLastDarkTheme(mode); lastDarkTheme.value = normalized; configRepository.setString('VRCX_lastDarkTheme', normalized); } const { isDark } = changeAppThemeStyle(mode); isDarkMode.value = isDark; vrStore.updateVRConfigVars(); updateTrustColor(undefined, undefined); } /** * */ function toggleThemeMode() { const nextMode = isDarkMode.value ? 'light' : resolveLastDarkTheme(lastDarkTheme.value); setThemeMode(nextMode); } /** * * @param value */ function normalizeAppFontFamily(value) { if (value === 'custom') return 'custom'; return APP_FONT_FAMILIES.includes(value) ? value : APP_FONT_DEFAULT_KEY; } /** * * @param value */ function normalizeAppCjkFontPack(value) { return APP_CJK_FONT_PACKS.includes(value) ? value : APP_CJK_FONT_PACK_DEFAULT_KEY; } /** * * @param value */ function setAppFontFamily(value) { const normalized = normalizeAppFontFamily(value); appFontFamily.value = normalized; configRepository.setString('VRCX_fontFamily', normalized); applyAppFontFamily(normalized, customFontFamily.value); } function setCustomFontFamily(value) { customFontFamily.value = value; configRepository.setString('VRCX_customFontFamily', value); if (appFontFamily.value === 'custom') { applyAppFontFamily('custom', value); } } /** * * @param value */ function setAppCjkFontPack(value) { const normalized = normalizeAppCjkFontPack(value); appCjkFontPack.value = normalized; configRepository.setString('VRCX_cjkFontPack', normalized); applyAppCjkFontPack(normalized); } /** * */ function setDisplayVRCPlusIconsAsAvatar() { displayVRCPlusIconsAsAvatar.value = !displayVRCPlusIconsAsAvatar.value; configRepository.setBool( 'displayVRCPlusIconsAsAvatar', displayVRCPlusIconsAsAvatar.value ); } /** * */ function setNotificationIconDot() { notificationIconDot.value = !notificationIconDot.value; configRepository.setBool( 'VRCX_notificationIconDot', notificationIconDot.value ); uiStore.updateTrayIconNotify(); } /** * */ function setHideNicknames() { hideNicknames.value = !hideNicknames.value; configRepository.setBool('VRCX_hideNicknames', hideNicknames.value); } /** * */ function setShowInstanceIdInLocation() { showInstanceIdInLocation.value = !showInstanceIdInLocation.value; configRepository.setBool( 'VRCX_showInstanceIdInLocation', showInstanceIdInLocation.value ); } /** * */ function setIsAgeGatedInstancesVisible() { isAgeGatedInstancesVisible.value = !isAgeGatedInstancesVisible.value; configRepository.setBool( 'VRCX_isAgeGatedInstancesVisible', isAgeGatedInstancesVisible.value ); } /** * */ function setSortFavorites() { sortFavorites.value = !sortFavorites.value; configRepository.setBool('VRCX_sortFavorites', sortFavorites.value); } /** * */ function setInstanceUsersSortAlphabetical() { instanceUsersSortAlphabetical.value = !instanceUsersSortAlphabetical.value; configRepository.setBool( 'VRCX_instanceUsersSortAlphabetical', instanceUsersSortAlphabetical.value ); } /** * * @param size */ function setTablePageSize(size) { const processedSize = clampInt(size, 1, MAX_TABLE_PAGE_SIZE); tablePageSize.value = processedSize; configRepository.setInt('VRCX_tablePageSize', processedSize); return processedSize; } /** * * @param input */ function normalizeTablePageSizes(input) { const values = ( Array.isArray(input) ? input : DEFAULT_TABLE_PAGE_SIZES ) .map((v) => parseInt(v, 10)) .filter((v) => v > 0 && v <= MAX_TABLE_PAGE_SIZE); const uniqueSorted = Array.from(new Set(values)).sort( (a, b) => a - b ); return uniqueSorted.length ? uniqueSorted : [...DEFAULT_TABLE_PAGE_SIZES]; } /** * @param {Array} sizes */ function setTablePageSizes(sizes) { tablePageSizes.value = normalizeTablePageSizes(sizes); configRepository.setString( 'VRCX_tablePageSizes', JSON.stringify(tablePageSizes.value) ); if (!tablePageSizes.value.includes(tablePageSize.value)) { setTablePageSize(tablePageSizes.value[0]); } } /** * */ function setDtHour12() { dtHour12.value = !dtHour12.value; configRepository.setBool('VRCX_dtHour12', dtHour12.value); } /** * */ function setDtIsoFormat() { dtIsoFormat.value = !dtIsoFormat.value; configRepository.setBool('VRCX_dtIsoFormat', dtIsoFormat.value); } /** * @param {number} value - 0 (Sunday), 1 (Monday), or 6 (Saturday) */ function setWeekStartsOn(value) { const v = [0, 1, 6].includes(value) ? value : 1; weekStartsOn.value = v; configRepository.setInt('VRCX_weekStartsOn', v); } /** * @param {string} method */ function setSidebarSortMethod1(method) { sidebarSortMethod1.value = method; handleSaveSidebarSortOrder(); } /** * @param {string} method */ function setSidebarSortMethod2(method) { sidebarSortMethod2.value = method; handleSaveSidebarSortOrder(); } /** * @param {string} method */ function setSidebarSortMethod3(method) { sidebarSortMethod3.value = method; handleSaveSidebarSortOrder(); } /** * @param {Array} methods */ function setSidebarSortMethods(methods) { sidebarSortMethods.value = methods; configRepository.setString( 'VRCX_sidebarSortMethods', JSON.stringify(methods) ); } /** * * @param collapsed */ function setNavCollapsed(collapsed) { isNavCollapsed.value = collapsed; configRepository.setBool('VRCX_navIsCollapsed', collapsed); } /** * */ function toggleNavCollapsed() { setNavCollapsed(!isNavCollapsed.value); } /** * * @param widthOrArray */ function setNavWidth(widthOrArray) { let width = null; if (Array.isArray(widthOrArray) && widthOrArray.length) { width = widthOrArray[widthOrArray.length - 1]; } else if (typeof widthOrArray === 'number') { width = widthOrArray; } if (width) { requestAnimationFrame(() => { navWidth.value = clampInt(width, 64, 480); configRepository.setInt( 'VRCX_navPanelWidth', navWidth.value ); }); } } /** * */ function setIsSidebarGroupByInstance() { isSidebarGroupByInstance.value = !isSidebarGroupByInstance.value; configRepository.setBool( 'VRCX_sidebarGroupByInstance', isSidebarGroupByInstance.value ); } /** * */ function setIsHideFriendsInSameInstance() { isHideFriendsInSameInstance.value = !isHideFriendsInSameInstance.value; configRepository.setBool( 'VRCX_hideFriendsInSameInstance', isHideFriendsInSameInstance.value ); } /** * */ function setIsSameInstanceAboveFavorites() { isSameInstanceAboveFavorites.value = !isSameInstanceAboveFavorites.value; configRepository.setBool( 'VRCX_sameInstanceAboveFavorites', isSameInstanceAboveFavorites.value ); } /** * */ function setIsSidebarDivideByFriendGroup() { isSidebarDivideByFriendGroup.value = !isSidebarDivideByFriendGroup.value; configRepository.setBool( 'VRCX_sidebarDivideByFriendGroup', isSidebarDivideByFriendGroup.value ); } /** * @param {string[]} value */ function setSidebarFavoriteGroups(value) { sidebarFavoriteGroups.value = value; configRepository.setString( 'VRCX_sidebarFavoriteGroups', JSON.stringify(value) ); } /** * @param {string[]} value */ function setSidebarFavoriteGroupOrder(value) { sidebarFavoriteGroupOrder.value = value; configRepository.setString( 'VRCX_sidebarFavoriteGroupOrder', JSON.stringify(value) ); } /** * */ function setHideUserNotes() { hideUserNotes.value = !hideUserNotes.value; configRepository.setBool('VRCX_hideUserNotes', hideUserNotes.value); } /** * */ function setHideUserMemos() { hideUserMemos.value = !hideUserMemos.value; configRepository.setBool('VRCX_hideUserMemos', hideUserMemos.value); } /** * */ function setHideUnfriends() { hideUnfriends.value = !hideUnfriends.value; configRepository.setBool('VRCX_hideUnfriends', hideUnfriends.value); } /** * */ function setRandomUserColours() { randomUserColours.value = !randomUserColours.value; configRepository.setBool( 'VRCX_randomUserColours', randomUserColours.value ); } /** * * @param value */ function normalizeTableDensity(value) { if ( value === 'compact' || value === 'comfortable' || value === 'standard' ) { return value; } return 'standard'; } /** * * @param density */ function setTableDensity(density) { const normalized = normalizeTableDensity(density); tableDensity.value = normalized; applyTableDensity(tableDensity.value); configRepository.setString('VRCX_tableDensity', tableDensity.value); } /** * */ function toggleStripedDataTable() { isDataTableStriped.value = !isDataTableStriped.value; configRepository.setBool( 'VRCX_dataTableStriped', isDataTableStriped.value ); } // FIXME: this is nasty, there should be a better way of doing this /** * */ function applyPointerHoverClass() { const classList = document.documentElement.classList; classList.remove('force-pointer-on-hover'); if (showPointerOnHover.value) { classList.add('force-pointer-on-hover'); } } /** * */ function togglePointerOnHover() { showPointerOnHover.value = !showPointerOnHover.value; configRepository.setBool( 'VRCX_showPointerOnHover', showPointerOnHover.value ); applyPointerHoverClass(); } /** * */ function applyAccessibleStatusClass() { const classList = document.documentElement.classList; classList.remove('accessible-status-indicators'); if (accessibleStatusIndicators.value) { classList.add('accessible-status-indicators'); } } /** * */ function toggleAccessibleStatusIndicators() { accessibleStatusIndicators.value = !accessibleStatusIndicators.value; configRepository.setBool( 'VRCX_accessibleStatusIndicators', accessibleStatusIndicators.value ); applyAccessibleStatusClass(); } /** * */ function applyOfficialStatusColorsClass() { const classList = document.documentElement.classList; classList.remove('vrcx-status-colors'); if (!useOfficialStatusColors.value) { classList.add('vrcx-status-colors'); } } /** * */ function toggleOfficialStatusColors() { useOfficialStatusColors.value = !useOfficialStatusColors.value; configRepository.setBool( 'VRCX_useOfficialStatusColors', useOfficialStatusColors.value ); applyOfficialStatusColorsClass(); } /** * */ function setShowNewDashboardButton() { showNewDashboardButton.value = !showNewDashboardButton.value; configRepository.setBool( 'VRCX_showNewDashboardButton', showNewDashboardButton.value ); } /** * @param {object} color */ function setTrustColor(color) { // @ts-ignore trustColor.value = color; configRepository.setString( 'VRCX_trustColor', JSON.stringify(trustColor.value) ); } /** * */ function handleSaveSidebarSortOrder() { if (sidebarSortMethod1.value === sidebarSortMethod2.value) { sidebarSortMethod2.value = ''; } if (sidebarSortMethod1.value === sidebarSortMethod3.value) { sidebarSortMethod3.value = ''; } if (sidebarSortMethod2.value === sidebarSortMethod3.value) { sidebarSortMethod3.value = ''; } if (!sidebarSortMethod1.value) { sidebarSortMethod2.value = ''; } if (!sidebarSortMethod2.value) { sidebarSortMethod3.value = ''; } const sidebarSortMethods = [ sidebarSortMethod1.value, sidebarSortMethod2.value, sidebarSortMethod3.value ]; setSidebarSortMethods(sidebarSortMethods); } /** * */ async function mergeOldSortMethodsSettings() { const orderFriendsGroupPrivate = await configRepository.getBool( 'orderFriendGroupPrivate' ); if (orderFriendsGroupPrivate !== null) { await configRepository.remove('orderFriendGroupPrivate'); const orderFriendsGroupStatus = await configRepository.getBool( 'orderFriendsGroupStatus' ); await configRepository.remove('orderFriendsGroupStatus'); const orderFriendsGroupGPS = await configRepository.getBool( 'orderFriendGroupGPS' ); await configRepository.remove('orderFriendGroupGPS'); const orderOnlineFor = await configRepository.getBool('orderFriendGroup0'); await configRepository.remove('orderFriendGroup0'); await configRepository.remove('orderFriendGroup1'); await configRepository.remove('orderFriendGroup2'); await configRepository.remove('orderFriendGroup3'); const sortOrder = []; if (orderFriendsGroupPrivate) { sortOrder.push('Sort Private to Bottom'); } if (orderFriendsGroupStatus) { sortOrder.push('Sort by Status'); } if (orderOnlineFor && orderFriendsGroupGPS) { sortOrder.push('Sort by Time in Instance'); } if (!orderOnlineFor) { sortOrder.push('Sort Alphabetically'); } if (sortOrder.length > 0) { while (sortOrder.length < 3) { sortOrder.push(''); } sidebarSortMethods.value = sortOrder; sidebarSortMethod1.value = sortOrder[0]; sidebarSortMethod2.value = sortOrder[1]; sidebarSortMethod3.value = sortOrder[2]; } setSidebarSortMethods(sortOrder); } } const clampLimit = (value, min, max) => { const n = Number.parseInt(value, 10); if (!Number.isFinite(n)) { return null; } if (n < min || n > max) { return null; } return n; }; /** * */ function showTableLimitsDialog() { tableLimitsDialog.value.maxTableSize = Number( vrcxStore.maxTableSize ?? 500 ); tableLimitsDialog.value.searchLimit = Number( vrcxStore.searchLimit ?? 50000 ); tableLimitsDialog.value.visible = true; } /** * */ function closeTableLimitsDialog() { tableLimitsDialog.value.visible = false; } /** * */ async function saveTableLimitsDialog() { const nextMaxTableSize = clampLimit( tableLimitsDialog.value.maxTableSize, TABLE_MAX_SIZE_MIN, TABLE_MAX_SIZE_MAX ); if (nextMaxTableSize === null) { return; } const nextSearchLimit = clampLimit( tableLimitsDialog.value.searchLimit, SEARCH_LIMIT_MIN, SEARCH_LIMIT_MAX ); if (nextSearchLimit === null) { return; } vrcxStore.setMaxTableSize(nextMaxTableSize); await configRepository.setInt( 'VRCX_maxTableSize_v2', vrcxStore.maxTableSize ); database.setMaxTableSize(vrcxStore.maxTableSize); vrcxStore.setSearchLimit(nextSearchLimit); await configRepository.setInt( 'VRCX_searchLimit', vrcxStore.searchLimit ); database.setSearchTableSize(vrcxStore.searchLimit); feedStore.feedTableLookup(); gameLogStore.gameLogTableLookup(); tableLimitsDialog.value.visible = false; } /** * */ async function tryInitUserColours() { if (!randomUserColours.value) { return; } const colour = await getNameColour( userStore.currentUser.id, isDarkMode.value ); userStore.setCurrentUserColour(colour); await userColourInit(); } /** * * @param density */ function applyTableDensity(density) { const classList = document.documentElement.classList; classList.remove('is-compact-table', 'is-comfortable-table'); if (density === 'compact') { classList.add('is-compact-table'); } if (density === 'comfortable') { classList.add('is-comfortable-table'); } } return { appLanguage, themeMode, isDarkMode, appFontFamily, appCjkFontPack, displayVRCPlusIconsAsAvatar, hideNicknames, showInstanceIdInLocation, isAgeGatedInstancesVisible, sortFavorites, instanceUsersSortAlphabetical, tablePageSize, tablePageSizes, dtHour12, dtIsoFormat, weekStartsOn, sidebarSortMethod1, sidebarSortMethod2, sidebarSortMethod3, sidebarSortMethods, navWidth, isSidebarGroupByInstance, isHideFriendsInSameInstance, isSameInstanceAboveFavorites, isSidebarDivideByFriendGroup, sidebarFavoriteGroups, sidebarFavoriteGroupOrder, hideUserNotes, hideUserMemos, hideUnfriends, randomUserColours, tableDensity, trustColor, currentCulture, isSideBarTabShow, notificationIconDot, isNavCollapsed, isDataTableStriped, showPointerOnHover, accessibleStatusIndicators, useOfficialStatusColors, showNewDashboardButton, tableLimitsDialog, TABLE_MAX_SIZE_MIN, TABLE_MAX_SIZE_MAX, SEARCH_LIMIT_MIN, SEARCH_LIMIT_MAX, setAppLanguage, setDisplayVRCPlusIconsAsAvatar, setHideNicknames, setShowInstanceIdInLocation, setIsAgeGatedInstancesVisible, setSortFavorites, setInstanceUsersSortAlphabetical, setTablePageSize, setTablePageSizes, setDtHour12, setDtIsoFormat, setWeekStartsOn, setSidebarSortMethod1, setSidebarSortMethod2, setSidebarSortMethod3, setSidebarSortMethods, setNavWidth, setIsSidebarGroupByInstance, setIsHideFriendsInSameInstance, setIsSameInstanceAboveFavorites, setIsSidebarDivideByFriendGroup, setSidebarFavoriteGroups, setSidebarFavoriteGroupOrder, setHideUserNotes, setHideUserMemos, setHideUnfriends, setRandomUserColours, toggleStripedDataTable, togglePointerOnHover, toggleAccessibleStatusIndicators, toggleOfficialStatusColors, setShowNewDashboardButton, setTableDensity, setTrustColor, tryInitUserColours, updateTrustColor, userColourInit, applyUserTrustLevel, changeAppLanguage, showTableLimitsDialog, closeTableLimitsDialog, saveTableLimitsDialog, setNotificationIconDot, applyTableDensity, setNavCollapsed, toggleNavCollapsed, setAppFontFamily, customFontFamily, setCustomFontFamily, setAppCjkFontPack, setThemeMode, toggleThemeMode }; } );