Files
VRCX/src/views/Settings/components/Tabs/AdvancedTab.vue
2026-03-19 18:42:43 +09:00

546 lines
25 KiB
Vue

<template>
<div class="flex flex-col gap-10 py-2">
<SettingsGroup :title="t('view.settings.advanced.advanced.vrchat_settings.header')">
<SettingsItem
:label="t('view.settings.advanced.advanced.relaunch_vrchat.header')"
:description="t('view.settings.advanced.advanced.relaunch_vrchat.description')">
<Switch :model-value="relaunchVRChatAfterCrash" @update:modelValue="setRelaunchVRChatAfterCrash" />
</SettingsItem>
<SettingsItem
:label="t('view.settings.advanced.advanced.vrchat_quit_fix.header')"
:description="t('view.settings.advanced.advanced.vrchat_quit_fix.description')">
<Switch :model-value="vrcQuitFix" @update:modelValue="setVrcQuitFix" />
</SettingsItem>
<SettingsItem
:label="t('view.settings.advanced.advanced.auto_cache_management.header')"
:description="t('view.settings.advanced.advanced.auto_cache_management.description')">
<Switch :model-value="autoSweepVRChatCache" @update:modelValue="setAutoSweepVRChatCache" />
</SettingsItem>
<SettingsItem
:label="t('view.settings.advanced.advanced.self_invite.header')"
:description="t('view.settings.advanced.advanced.self_invite.description')">
<Switch :model-value="selfInviteOverride" @update:modelValue="setSelfInviteOverride" />
</SettingsItem>
</SettingsGroup>
<SettingsGroup :title="t('view.settings.advanced_groups.security.header')">
<SettingsItem
:label="t('view.settings.advanced.advanced.primary_password.header')"
:description="t('view.settings.advanced.advanced.primary_password.description')">
<Switch
:model-value="enablePrimaryPassword"
:disabled="!enablePrimaryPassword"
@update:modelValue="enablePrimaryPasswordChange" />
</SettingsItem>
</SettingsGroup>
<SettingsGroup :title="t('view.settings.general.logging.header')">
<SettingsItem :label="t('view.settings.advanced.advanced.cache_debug.udon_exception_logging')">
<Switch :model-value="udonExceptionLogging" @update:modelValue="setUdonExceptionLogging" />
</SettingsItem>
<SettingsItem :label="t('view.settings.general.logging.resource_load')">
<Switch :model-value="logResourceLoad" @update:modelValue="setLogResourceLoad" />
</SettingsItem>
<SettingsItem :label="t('view.settings.general.logging.empty_avatar')">
<Switch :model-value="logEmptyAvatars" @update:modelValue="setLogEmptyAvatars" />
</SettingsItem>
<SettingsItem :label="t('view.settings.general.logging.auto_login_delay')">
<Switch :model-value="autoLoginDelayEnabled" @update:modelValue="setAutoLoginDelayEnabled" />
</SettingsItem>
<SettingsItem
v-if="autoLoginDelayEnabled"
:label="t('view.settings.general.logging.auto_login_delay_button')">
<Button size="sm" variant="outline" @click="promptAutoLoginDelaySeconds">
{{ t('view.settings.general.logging.auto_login_delay_button') }}
</Button>
</SettingsItem>
</SettingsGroup>
<template v-if="!isLinux">
<SettingsGroup :title="t('view.settings.advanced.advanced.app_launcher.header')">
<SettingsItem :label="t('view.settings.advanced.advanced.app_launcher.folder')">
<Button size="sm" variant="outline" @click="openShortcutFolder()">{{
t('view.settings.advanced.advanced.app_launcher.folder')
}}</Button>
</SettingsItem>
<SettingsItem
:label="t('view.settings.advanced.advanced.remote_database.enable')"
:description="t('view.settings.advanced.advanced.app_launcher.folder_tooltip')">
<Switch :model-value="enableAppLauncher" @update:modelValue="setEnableAppLauncher" />
</SettingsItem>
<SettingsItem :label="t('view.settings.advanced.advanced.app_launcher.auto_close')">
<Switch
:model-value="enableAppLauncherAutoClose"
@update:modelValue="setEnableAppLauncherAutoClose" />
</SettingsItem>
<SettingsItem :label="t('view.settings.advanced.advanced.app_launcher.run_process_once')">
<Switch
:model-value="enableAppLauncherRunProcessOnce"
@update:modelValue="setEnableAppLauncherRunProcessOnce" />
</SettingsItem>
</SettingsGroup>
</template>
<SettingsGroup :title="t('view.settings.advanced.advanced.launch_commands.header')">
<SettingsItem
:label="t('view.settings.advanced.advanced.launch_commands.show_confirmation_on_switch_avatar_enable')"
:description="
t('view.settings.advanced.advanced.launch_commands.show_confirmation_on_switch_avatar_tooltip')
">
<Switch
:model-value="showConfirmationOnSwitchAvatar"
@update:modelValue="setShowConfirmationOnSwitchAvatar" />
</SettingsItem>
<div class="flex gap-2">
<Button
size="sm"
variant="outline"
@click="openExternalLink('https://github.com/vrcx-team/VRCX/wiki/Launch-parameters-&-VRCX.json')"
>{{ t('view.settings.advanced.advanced.launch_commands.docs') }}</Button
>
<Button
size="sm"
variant="outline"
@click="openExternalLink('https://github.com/Myrkie/open-in-vrcx')"
>{{ t('view.settings.advanced.advanced.launch_commands.website_userscript') }}</Button
>
</div>
</SettingsGroup>
<SettingsGroup :title="t('view.settings.advanced.advanced.cache_debug.header')">
<div class="flex gap-2 flex-wrap">
<Button size="sm" variant="outline" @click="clearVRCXCache">{{
t('view.settings.advanced.advanced.cache_debug.clear_cache')
}}</Button>
<Button size="sm" variant="outline" @click="promptAutoClearVRCXCacheFrequency">{{
t('view.settings.advanced.advanced.cache_debug.auto_clear_cache')
}}</Button>
<Button size="sm" variant="outline" @click="refreshCacheSize">{{
t('view.settings.advanced.advanced.cache_debug.refresh_cache')
}}</Button>
</div>
<SettingsItem
:label="`${t('view.settings.advanced.advanced.cache_debug.disable_gamelog')} ${t('view.settings.advanced.advanced.cache_debug.disable_gamelog_notice')}`">
<Switch :model-value="gameLogDisabled" @update:modelValue="disableGameLogDialog()" />
</SettingsItem>
<div class="flex flex-col gap-1 text-sm">
<span
>{{ t('view.settings.advanced.advanced.cache_debug.user_cache') }}
<span v-text="cacheSize.cachedUsers"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.cache_debug.world_cache') }}
<span v-text="cacheSize.cachedWorlds"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.cache_debug.avatar_cache') }}
<span v-text="cacheSize.cachedAvatars"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.cache_debug.group_cache') }}
<span v-text="cacheSize.cachedGroups"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.cache_debug.avatar_name_cache') }}
<span v-text="cacheSize.cachedAvatarNames"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.cache_debug.instance_cache') }}
<span v-text="cacheSize.cachedInstances"></span
></span>
</div>
<SettingsItem :label="t('view.settings.advanced.advanced.cache_debug.show_console')">
<Button size="sm" variant="outline" @click="showConsole">{{
t('view.settings.advanced.advanced.cache_debug.show_console')
}}</Button>
</SettingsItem>
</SettingsGroup>
<SettingsGroup :title="t('view.settings.advanced_groups.database.header')">
<SettingsItem :label="t('view.settings.advanced.advanced.sqlite_table_size.refresh')">
<Button size="sm" variant="outline" @click="getSqliteTableSizes">{{
t('view.settings.advanced.advanced.sqlite_table_size.refresh')
}}</Button>
</SettingsItem>
<div class="flex flex-col gap-1 text-sm">
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.gps') }}
<span v-text="sqliteTableSizes.gps"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.status') }}
<span v-text="sqliteTableSizes.status"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.bio') }}
<span v-text="sqliteTableSizes.bio"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.avatar') }}
<span v-text="sqliteTableSizes.avatar"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.online_offline') }}
<span v-text="sqliteTableSizes.onlineOffline"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.friend_log_history') }}
<span v-text="sqliteTableSizes.friendLogHistory"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.notification') }}
<span v-text="sqliteTableSizes.notification"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.location') }}
<span v-text="sqliteTableSizes.location"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.join_leave') }}
<span v-text="sqliteTableSizes.joinLeave"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.portal_spawn') }}
<span v-text="sqliteTableSizes.portalSpawn"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.video_play') }}
<span v-text="sqliteTableSizes.videoPlay"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.event') }}
<span v-text="sqliteTableSizes.event"></span
></span>
</div>
<SettingsItem
:label="t('view.settings.advanced.advanced.database_cleanup.auto_cleanup')"
:description="t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_description')">
<Select :model-value="avatarAutoCleanup" @update:modelValue="setAvatarAutoCleanup">
<SelectTrigger class="w-36">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="Off">{{
t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_off')
}}</SelectItem>
<SelectItem value="30">{{
t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_30')
}}</SelectItem>
<SelectItem value="90">{{
t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_90')
}}</SelectItem>
<SelectItem value="180">{{
t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_180')
}}</SelectItem>
<SelectItem value="365">{{
t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_365')
}}</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</SettingsItem>
<SettingsItem :label="t('view.settings.advanced.advanced.database_cleanup.purge_button')">
<Button size="sm" variant="outline" @click="isPurgeDialogVisible = true">
<Trash2 class="h-4 w-4 mr-1" />
{{ t('view.settings.advanced.advanced.database_cleanup.purge') }}
</Button>
</SettingsItem>
</SettingsGroup>
<Dialog
:open="isPurgeDialogVisible"
@update:open="
(open) => {
if (!open) isPurgeDialogVisible = false;
}
">
<DialogContent class="x-dialog sm:max-w-md">
<DialogHeader>
<DialogTitle>{{
t('view.settings.advanced.advanced.database_cleanup.purge_confirm_title')
}}</DialogTitle>
</DialogHeader>
<Alert variant="warning" class="mb-3">
<TriangleAlert />
<AlertDescription>
{{ t('view.settings.advanced.advanced.database_cleanup.purge_confirm_alert') }}
</AlertDescription>
</Alert>
<div class="flex flex-col gap-1 text-sm text-muted-foreground mb-3">
<p>{{ t('view.settings.advanced.advanced.database_cleanup.purge_confirm_description_1') }}</p>
<p>{{ t('view.settings.advanced.advanced.database_cleanup.purge_confirm_description_2') }}</p>
<p>{{ t('view.settings.advanced.advanced.database_cleanup.purge_confirm_description_3') }}</p>
</div>
<SettingsItem :label="t('view.settings.advanced.advanced.database_cleanup.purge_older_than')">
<Select v-model="selectedPurgePeriod">
<SelectTrigger class="w-36">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="180">{{
t('view.settings.advanced.advanced.database_cleanup.purge_option_180')
}}</SelectItem>
<SelectItem value="365">{{
t('view.settings.advanced.advanced.database_cleanup.purge_option_365')
}}</SelectItem>
<SelectItem value="730">{{
t('view.settings.advanced.advanced.database_cleanup.purge_option_730')
}}</SelectItem>
<SelectItem value="all">{{
t('view.settings.advanced.advanced.database_cleanup.purge_option_all')
}}</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</SettingsItem>
<DialogFooter>
<Button variant="outline" size="sm" @click="isPurgeDialogVisible = false">
{{ t('confirm.cancel_button') }}
</Button>
<Button size="sm" variant="destructive" :disabled="purgeInProgress" @click="handlePurge">
<Trash2 class="h-4 w-4 mr-1" />
{{ t('view.settings.advanced.advanced.database_cleanup.purge_confirm_button') }}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<SettingsGroup :title="t('view.settings.advanced_groups.diagnostics.header')">
<SettingsGroup :title="t('view.profile.game_info.header')">
<div class="px-1 py-1">
<div class="flex-1 cursor-pointer" @click="getVisits">
<span class="block truncate font-medium text-sm leading-[18px]">{{
t('view.profile.game_info.online_users')
}}</span>
<span v-if="visits" class="block truncate text-xs text-muted-foreground">{{
t('view.profile.game_info.user_online', { count: visits })
}}</span>
<span v-else class="block truncate text-xs text-muted-foreground">{{
t('view.profile.game_info.refresh')
}}</span>
</div>
</div>
</SettingsGroup>
<SettingsGroup :title="t('view.profile.config_json')">
<div class="flex items-center gap-2">
<TooltipWrapper side="top" :content="t('view.profile.refresh_tooltip')">
<Button class="rounded-full" size="icon-sm" variant="outline" @click="refreshConfigTreeData()">
<RefreshCcw />
</Button>
</TooltipWrapper>
<TooltipWrapper side="top" :content="t('view.profile.clear_results_tooltip')">
<Button class="rounded-full" size="icon-sm" variant="outline" @click="configTreeData = {}">
<Trash2 />
</Button>
</TooltipWrapper>
</div>
<vue-json-pretty
v-if="Object.keys(configTreeData).length > 0"
:data="configTreeData"
:deep="2"
:theme="isDarkMode ? 'dark' : 'light'"
:height="800"
:dynamic-height="false"
virtual
show-icon />
</SettingsGroup>
</SettingsGroup>
<template v-if="branch === 'Nightly'">
<SettingsGroup :title="t('view.settings.advanced_groups.nightly.header')">
<SettingsItem
:label="t('view.settings.advanced.advanced.anonymous_error_reporting.header')"
:description="t('view.settings.advanced.advanced.anonymous_error_reporting.description')">
<Switch :model-value="sentryErrorReporting" @update:modelValue="setSentryErrorReporting()" />
</SettingsItem>
</SettingsGroup>
</template>
<RegistryBackupDialog />
<PhotonSettings v-if="photonLoggingEnabled" />
</div>
</template>
<script setup>
import { RefreshCcw, Trash2, TriangleAlert } from 'lucide-vue-next';
import { computed, reactive, ref } from 'vue';
import { Button } from '@/components/ui/button';
import { Switch } from '@/components/ui/switch';
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import VueJsonPretty from 'vue-json-pretty';
import {
useAdvancedSettingsStore,
useAppearanceSettingsStore,
useAuthStore,
useAvatarStore,
useGeneralSettingsStore,
useGroupStore,
useInstanceStore,
usePhotonStore,
useUiStore,
useUserStore,
useVRCXUpdaterStore,
useWorldStore
} from '@/stores';
import { authRequest, queryRequest } from '@/api';
import { disableGameLogDialog } from '@/coordinators/gameLogCoordinator';
import { clearVRCXCache } from '@/coordinators/vrcxCoordinator';
import { openExternalLink } from '@/shared/utils';
import PhotonSettings from '../PhotonSettings.vue';
import RegistryBackupDialog from '../../../Tools/dialogs/RegistryBackupDialog.vue';
import SettingsGroup from '../SettingsGroup.vue';
import SettingsItem from '../SettingsItem.vue';
import { TooltipWrapper } from '@/components/ui/tooltip';
const { t } = useI18n();
const advancedSettingsStore = useAdvancedSettingsStore();
const { enablePrimaryPasswordChange } = useAuthStore();
const { cachedConfig } = storeToRefs(useAuthStore());
const { showConsole } = useUiStore();
const generalSettingsStore = useGeneralSettingsStore();
const { udonExceptionLogging, logResourceLoad, logEmptyAvatars, autoLoginDelayEnabled } =
storeToRefs(generalSettingsStore);
const {
setUdonExceptionLogging,
setLogResourceLoad,
setLogEmptyAvatars,
setAutoLoginDelayEnabled,
promptAutoLoginDelaySeconds
} = generalSettingsStore;
const { cachedUsers } = useUserStore();
const { cachedWorlds } = useWorldStore();
const { cachedAvatars, cachedAvatarNames } = useAvatarStore();
const { cachedGroups } = useGroupStore();
const { cachedInstances } = useInstanceStore();
const { photonLoggingEnabled } = storeToRefs(usePhotonStore());
const { branch } = storeToRefs(useVRCXUpdaterStore());
const { isDarkMode } = storeToRefs(useAppearanceSettingsStore());
const {
enablePrimaryPassword,
relaunchVRChatAfterCrash,
vrcQuitFix,
autoSweepVRChatCache,
selfInviteOverride,
enableAppLauncher,
enableAppLauncherAutoClose,
enableAppLauncherRunProcessOnce,
showConfirmationOnSwitchAvatar,
gameLogDisabled,
sqliteTableSizes,
avatarAutoCleanup,
purgeInProgress,
sentryErrorReporting
} = storeToRefs(advancedSettingsStore);
const {
setRelaunchVRChatAfterCrash,
setVrcQuitFix,
setAutoSweepVRChatCache,
setSelfInviteOverride,
setEnableAppLauncher,
setEnableAppLauncherAutoClose,
setEnableAppLauncherRunProcessOnce,
setShowConfirmationOnSwitchAvatar,
getSqliteTableSizes,
setAvatarAutoCleanup,
purgeAvatarFeedData,
promptAutoClearVRCXCacheFrequency,
setSentryErrorReporting
} = advancedSettingsStore;
const configTreeData = ref({});
const visits = ref(0);
const selectedPurgePeriod = ref('180');
const isPurgeDialogVisible = ref(false);
const cacheSize = reactive({
cachedUsers: 0,
cachedWorlds: 0,
cachedAvatars: 0,
cachedGroups: 0,
cachedAvatarNames: 0,
cachedInstances: 0
});
const isLinux = computed(() => LINUX);
function handlePurge() {
const days = selectedPurgePeriod.value === 'all' ? null : parseInt(selectedPurgePeriod.value, 10);
isPurgeDialogVisible.value = false;
purgeAvatarFeedData(days);
}
/**
*
*/
function openShortcutFolder() {
AppApi.OpenShortcutFolder();
}
/**
*
*/
function refreshCacheSize() {
cacheSize.cachedUsers = cachedUsers.size;
cacheSize.cachedWorlds = cachedWorlds.size;
cacheSize.cachedAvatars = cachedAvatars.size;
cacheSize.cachedGroups = cachedGroups.size;
cacheSize.cachedAvatarNames = cachedAvatarNames.size;
cacheSize.cachedInstances = cachedInstances.size;
}
/**
*
*/
async function refreshConfigTreeData() {
await authRequest.getConfig();
configTreeData.value = cachedConfig.value;
}
/**
*
*/
function getVisits() {
queryRequest.fetch('visits').then((args) => {
visits.value = args.json;
});
}
</script>