refactor: split settings tab components into smaller components

This commit is contained in:
pa
2025-10-16 10:57:57 +09:00
committed by Natsumi
parent 3a2bd56bbf
commit 6ba7a1c3c4
8 changed files with 1727 additions and 1739 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,519 @@
<template>
<div>
<div class="options-container" style="margin-top: 0">
<span class="header">{{ t('view.settings.advanced.advanced.header') }}</span>
<div class="options-container-item" style="margin-top: 15px">
<el-button-group>
<el-button size="small" :icon="Operation" @click="showVRChatConfig()">VRChat config.json</el-button>
<el-button size="small" :icon="Operation" @click="showLaunchOptions()">{{
t('view.settings.advanced.advanced.launch_options')
}}</el-button>
<el-button size="small" :icon="Goods" @click="showRegistryBackupDialog()">{{
t('view.settings.advanced.advanced.vrc_registry_backup')
}}</el-button>
</el-button-group>
</div>
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.advanced.advanced.common_folders') }}</span>
<div class="options-container-item" style="margin-top: 15px">
<el-button-group>
<el-button size="small" :icon="Folder" @click="openVrcxAppDataFolder()">VRCX Data</el-button>
<el-button size="small" :icon="Folder" @click="openVrcAppDataFolder()">VRChat Data</el-button>
<el-button size="small" :icon="Folder" @click="openCrashVrcCrashDumps()">Crash Dumps</el-button>
</el-button-group>
</div>
</div>
<div class="options-container">
<span class="sub-header">{{ t('view.settings.advanced.advanced.primary_password.header') }}</span>
<simple-switch
:label="t('view.settings.advanced.advanced.primary_password.description')"
:value="enablePrimaryPassword"
:disabled="!enablePrimaryPassword"
:long-label="true"
@change="enablePrimaryPasswordChange" />
<span class="sub-header">{{ t('view.settings.advanced.advanced.relaunch_vrchat.header') }}</span>
<simple-switch
:label="t('view.settings.advanced.advanced.relaunch_vrchat.description')"
:value="relaunchVRChatAfterCrash"
:long-label="true"
@change="
setRelaunchVRChatAfterCrash();
saveOpenVROption();
" />
<span class="sub-header">{{ t('view.settings.advanced.advanced.vrchat_quit_fix.header') }}</span>
<simple-switch
:label="t('view.settings.advanced.advanced.vrchat_quit_fix.description')"
:value="vrcQuitFix"
:long-label="true"
@change="
setVrcQuitFix();
saveOpenVROption();
" />
<span class="sub-header">{{ t('view.settings.advanced.advanced.auto_cache_management.header') }}</span>
<simple-switch
:label="t('view.settings.advanced.advanced.auto_cache_management.description')"
:value="autoSweepVRChatCache"
:long-label="true"
@change="
setAutoSweepVRChatCache();
saveOpenVROption();
" />
<span class="sub-header">{{ t('view.settings.advanced.advanced.self_invite.header') }}</span>
<simple-switch
:label="t('view.settings.advanced.advanced.self_invite.description')"
:value="selfInviteOverride"
:long-label="true"
@change="
setSelfInviteOverride();
saveOpenVROption();
" />
<div v-if="branch === 'Nightly'">
<span class="sub-header">Anonymous Error Reporting (Nightly Only)</span>
<simple-switch
label="Help improve VRCX by sending anonymous error reports. Only collects crash and error information, no personal data or VRChat information is collected."
:value="sentryErrorReporting"
:long-label="true"
@change="setSentryErrorReporting()" />
</div>
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.advanced.advanced.remote_database.header') }}</span>
<simple-switch
:label="t('view.settings.advanced.advanced.remote_database.enable')"
:value="avatarRemoteDatabase"
:long-label="true"
@change="
setAvatarRemoteDatabase(!avatarRemoteDatabase);
saveOpenVROption();
" />
<div class="options-container-item">
<el-button size="small" :icon="User" @click="showAvatarProviderDialog">{{
t('view.settings.advanced.advanced.remote_database.avatar_database_provider')
}}</el-button>
</div>
</div>
<template v-if="!isLinux">
<div class="options-container">
<span class="header">{{ t('view.settings.advanced.advanced.app_launcher.header') }}</span>
<br />
<el-button size="small" :icon="Folder" style="margin-top: 5px" @click="openShortcutFolder()">{{
t('view.settings.advanced.advanced.app_launcher.folder')
}}</el-button>
<simple-switch
:label="t('view.settings.advanced.advanced.remote_database.enable')"
:value="enableAppLauncher"
:tooltip="t('view.settings.advanced.advanced.app_launcher.folder_tooltip')"
:long-label="true"
@change="setEnableAppLauncher" />
<simple-switch
:label="t('view.settings.advanced.advanced.app_launcher.auto_close')"
:value="enableAppLauncherAutoClose"
:long-label="true"
@change="setEnableAppLauncherAutoClose" />
<simple-switch
:label="t('view.settings.advanced.advanced.app_launcher.run_process_once')"
:value="enableAppLauncherRunProcessOnce"
:long-label="true"
@change="setEnableAppLauncherRunProcessOnce" />
</div>
</template>
<div class="options-container">
<span class="header">{{ t('view.settings.advanced.advanced.youtube_api.header') }}</span>
<simple-switch
:label="t('view.settings.advanced.advanced.youtube_api.enable')"
:value="youTubeApi"
:tooltip="t('view.settings.advanced.advanced.youtube_api.enable_tooltip')"
:long-label="true"
@change="changeYouTubeApi('VRCX_youtubeAPI')" />
<div class="options-container-item">
<el-button size="small" :icon="CaretRight" @click="showYouTubeApiDialog">{{
t('view.settings.advanced.advanced.youtube_api.youtube_api_key')
}}</el-button>
</div>
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.advanced.advanced.video_progress_pie.header') }}</span>
<simple-switch
:label="t('view.settings.advanced.advanced.video_progress_pie.enable')"
:value="progressPie"
:disabled="!openVR"
:tooltip="t('view.settings.advanced.advanced.video_progress_pie.enable_tooltip')"
:long-label="true"
@change="changeYouTubeApi('VRCX_progressPie')" />
<simple-switch
:label="t('view.settings.advanced.advanced.video_progress_pie.dance_world_only')"
:value="progressPieFilter"
:disabled="!openVR"
:long-label="true"
@change="changeYouTubeApi('VRCX_progressPieFilter')" />
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.advanced.advanced.launch_commands.header') }}</span>
<simple-switch
:label="t('view.settings.advanced.advanced.launch_commands.show_confirmation_on_switch_avatar_enable')"
:value="showConfirmationOnSwitchAvatar"
:tooltip="
t('view.settings.advanced.advanced.launch_commands.show_confirmation_on_switch_avatar_tooltip')
"
:long-label="true"
@change="setShowConfirmationOnSwitchAvatar" />
<div class="options-container-item">
<el-button
size="small"
:icon="Paperclip"
@click="openExternalLink('https://github.com/vrcx-team/VRCX/wiki/Launch-parameters-&-VRCX.json')"
>{{ t('view.settings.advanced.advanced.launch_commands.docs') }}</el-button
>
<el-button
size="small"
:icon="Paperclip"
@click="openExternalLink('https://github.com/Myrkie/open-in-vrcx')"
>{{ t('view.settings.advanced.advanced.launch_commands.website_userscript') }}</el-button
>
</div>
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.advanced.advanced.cache_debug.header') }}</span>
<br />
<div class="options-container-item">
<el-button size="small" :icon="DeleteFilled" @click="clearVRCXCache">{{
t('view.settings.advanced.advanced.cache_debug.clear_cache')
}}</el-button>
<el-button size="small" :icon="Timer" @click="promptAutoClearVRCXCacheFrequency">{{
t('view.settings.advanced.advanced.cache_debug.auto_clear_cache')
}}</el-button>
<el-button size="small" :icon="Refresh" @click="refreshCacheSize">{{
t('view.settings.advanced.advanced.cache_debug.refresh_cache')
}}</el-button>
</div>
<simple-switch
:label="`${t('view.settings.advanced.advanced.cache_debug.disable_gamelog')} ${t('view.settings.advanced.advanced.cache_debug.disable_gamelog_notice')}`"
:value="gameLogDisabled"
:long-label="true"
@change="disableGameLogDialog()" />
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.cache_debug.user_cache') }}
<span v-text="cacheSize.cachedUsers"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.cache_debug.world_cache') }}
<span v-text="cacheSize.cachedWorlds"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.cache_debug.avatar_cache') }}
<span v-text="cacheSize.cachedAvatars"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.cache_debug.group_cache') }}
<span v-text="cacheSize.cachedGroups"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.cache_debug.avatar_name_cache') }}
<span v-text="cacheSize.cachedAvatarNames"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.cache_debug.instance_cache') }}
<span v-text="cacheSize.cachedInstances"></span>
</span>
</div>
<div class="options-container-item">
<el-button size="small" :icon="Tickets" @click="showConsole">{{
t('view.settings.advanced.advanced.cache_debug.show_console')
}}</el-button>
</div>
</div>
<div class="options-container">
<span class="sub-header">{{ t('view.settings.advanced.advanced.sqlite_table_size.header') }}</span>
<div class="options-container-item">
<el-button size="small" :icon="Refresh" @click="getSqliteTableSizes">{{
t('view.settings.advanced.advanced.sqlite_table_size.refresh')
}}</el-button>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.sqlite_table_size.gps') }}
<span v-text="sqliteTableSizes.gps"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.sqlite_table_size.status') }}
<span v-text="sqliteTableSizes.status"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.sqlite_table_size.bio') }}
<span v-text="sqliteTableSizes.bio"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.sqlite_table_size.avatar') }}
<span v-text="sqliteTableSizes.avatar"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.sqlite_table_size.online_offline') }}
<span v-text="sqliteTableSizes.onlineOffline"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.sqlite_table_size.friend_log_history') }}
<span v-text="sqliteTableSizes.friendLogHistory"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.sqlite_table_size.notification') }}
<span v-text="sqliteTableSizes.notification"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.sqlite_table_size.location') }}
<span v-text="sqliteTableSizes.location"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.sqlite_table_size.join_leave') }}
<span v-text="sqliteTableSizes.joinLeave"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.sqlite_table_size.portal_spawn') }}
<span v-text="sqliteTableSizes.portalSpawn"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.sqlite_table_size.video_play') }}
<span v-text="sqliteTableSizes.videoPlay"></span>
</span>
</div>
<div class="options-container-item">
<span class="name">
{{ t('view.settings.advanced.advanced.sqlite_table_size.event') }}
<span v-text="sqliteTableSizes.event"></span>
</span>
</div>
</div>
<RegistryBackupDialog />
<YouTubeApiDialog v-model:isYouTubeApiDialogVisible="isYouTubeApiDialogVisible" />
<AvatarProviderDialog v-model:isAvatarProviderDialogVisible="isAvatarProviderDialogVisible" />
<PhotonSettings v-if="photonLoggingEnabled" />
</div>
</template>
<script setup>
import {
CaretRight,
DeleteFilled,
Folder,
Goods,
Operation,
Paperclip,
Refresh,
Tickets,
Timer,
User
} from '@element-plus/icons-vue';
import { computed, reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import {
useAdvancedSettingsStore,
useAuthStore,
useAvatarProviderStore,
useAvatarStore,
useGroupStore,
useInstanceStore,
useLaunchStore,
useNotificationsSettingsStore,
usePhotonStore,
useUserStore,
useVRCXUpdaterStore,
useVrStore,
useVrcxStore,
useWorldStore
} from '../../../../stores';
import { openExternalLink } from '../../../../shared/utils';
import AvatarProviderDialog from '../../dialogs/AvatarProviderDialog.vue';
import PhotonSettings from '../PhotonSettings.vue';
import RegistryBackupDialog from '../../dialogs/RegistryBackupDialog.vue';
import SimpleSwitch from '../SimpleSwitch.vue';
import YouTubeApiDialog from '../../dialogs/YouTubeApiDialog.vue';
const { t } = useI18n();
const advancedSettingsStore = useAdvancedSettingsStore();
const notificationsSettingsStore = useNotificationsSettingsStore();
const { saveOpenVROption, updateVRLastLocation, updateOpenVR } = useVrStore();
const { showLaunchOptions } = useLaunchStore();
const { enablePrimaryPasswordChange } = useAuthStore();
const { showConsole, clearVRCXCache, showRegistryBackupDialog } = useVrcxStore();
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 { openVR } = storeToRefs(notificationsSettingsStore);
const {
enablePrimaryPassword,
relaunchVRChatAfterCrash,
vrcQuitFix,
autoSweepVRChatCache,
selfInviteOverride,
avatarRemoteDatabase,
enableAppLauncher,
enableAppLauncherAutoClose,
enableAppLauncherRunProcessOnce,
youTubeApi,
progressPie,
progressPieFilter,
showConfirmationOnSwitchAvatar,
gameLogDisabled,
sqliteTableSizes,
sentryErrorReporting
} = storeToRefs(advancedSettingsStore);
const {
setRelaunchVRChatAfterCrash,
setVrcQuitFix,
setAutoSweepVRChatCache,
setSelfInviteOverride,
setAvatarRemoteDatabase,
setEnableAppLauncher,
setEnableAppLauncherAutoClose,
setEnableAppLauncherRunProcessOnce,
setShowConfirmationOnSwitchAvatar,
getSqliteTableSizes,
showVRChatConfig,
promptAutoClearVRCXCacheFrequency,
setSentryErrorReporting
} = advancedSettingsStore;
const { isAvatarProviderDialogVisible } = storeToRefs(useAvatarProviderStore());
const { showAvatarProviderDialog } = useAvatarProviderStore();
const isYouTubeApiDialogVisible = ref(false);
const cacheSize = reactive({
cachedUsers: 0,
cachedWorlds: 0,
cachedAvatars: 0,
cachedGroups: 0,
cachedAvatarNames: 0,
cachedInstances: 0
});
const isLinux = computed(() => LINUX);
function openVrcxAppDataFolder() {
AppApi.OpenVrcxAppDataFolder().then((result) => {
if (result) {
ElMessage({
message: 'Folder opened',
type: 'success'
});
} else {
ElMessage({
message: "Folder dosn't exist",
type: 'error'
});
}
});
}
function openVrcAppDataFolder() {
AppApi.OpenVrcAppDataFolder().then((result) => {
if (result) {
ElMessage({
message: 'Folder opened',
type: 'success'
});
} else {
ElMessage({
message: "Folder dosn't exist",
type: 'error'
});
}
});
}
function openCrashVrcCrashDumps() {
AppApi.OpenCrashVrcCrashDumps().then((result) => {
if (result) {
ElMessage({
message: 'Folder opened',
type: 'success'
});
} else {
ElMessage({
message: "Folder dosn't exist",
type: 'error'
});
}
});
}
function openShortcutFolder() {
AppApi.OpenShortcutFolder();
}
function showYouTubeApiDialog() {
isYouTubeApiDialogVisible.value = true;
}
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 changeYouTubeApi(configKey = '') {
if (configKey === 'VRCX_youtubeAPI') {
advancedSettingsStore.setYouTubeApi();
} else if (configKey === 'VRCX_progressPie') {
advancedSettingsStore.setProgressPie();
} else if (configKey === 'VRCX_progressPieFilter') {
advancedSettingsStore.setProgressPieFilter();
}
updateVRLastLocation();
updateOpenVR();
}
</script>

View File

@@ -0,0 +1,447 @@
<template>
<div>
<div class="options-container" style="margin-top: 0">
<span class="header">{{ t('view.settings.appearance.appearance.header') }}</span>
<div class="options-container-item">
<span class="name">{{ t('view.settings.appearance.appearance.language') }}</span>
<el-dropdown trigger="click" size="small" @click.stop>
<el-button size="small">
<span
>{{ messages[appLanguage]?.language }}
<el-icon class="el-icon--right"><ArrowDown /></el-icon
></span>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="(obj, language) in messages"
:key="language"
:class="{ 'is-active': appLanguage === language }"
@click="changeAppLanguage(language)"
v-text="obj.language" />
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="options-container-item">
<span class="name">{{ t('view.settings.appearance.appearance.theme_mode') }}</span>
<el-dropdown trigger="click" size="small" @click.stop>
<el-button size="small">
<span
>{{ t(`view.settings.appearance.appearance.theme_mode_${themeMode}`) }}
<el-icon class="el-icon--right"><ArrowDown /></el-icon
></span>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="(config, themeKey) in THEME_CONFIG"
:key="themeKey"
@click="saveThemeMode(themeKey)"
:class="{ 'is-active': themeMode === themeKey }">
{{ t(`view.settings.appearance.appearance.theme_mode_${themeKey}`) }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div v-if="!isLinux" class="options-container-item">
<span class="name">{{ t('view.settings.appearance.appearance.zoom') }}</span>
<el-input-number
v-model="zoomLevel"
size="small"
:precision="0"
style="width: 128px"
@change="setZoomLevel" />
</div>
<simple-switch
:label="t('view.settings.appearance.appearance.vrcplus_profile_icons')"
:value="displayVRCPlusIconsAsAvatar"
@change="
setDisplayVRCPlusIconsAsAvatar();
saveOpenVROption();
" />
<simple-switch
:label="t('view.settings.appearance.appearance.nicknames')"
:value="!hideNicknames"
@change="
setHideNicknames();
saveOpenVROption();
" />
<simple-switch
:label="t('view.settings.appearance.appearance.age_gated_instances')"
:value="isAgeGatedInstancesVisible"
@change="setIsAgeGatedInstancesVisible" />
<div class="options-container-item">
<span class="name">{{ t('view.settings.appearance.appearance.sort_favorite_by') }}</span>
<el-radio-group :model-value="sortFavorites" @change="saveSortFavoritesOption">
<el-radio :label="false">{{
t('view.settings.appearance.appearance.sort_favorite_by_name')
}}</el-radio>
<el-radio :label="true">{{
t('view.settings.appearance.appearance.sort_favorite_by_date')
}}</el-radio>
</el-radio-group>
</div>
<div class="options-container-item">
<span class="name">{{ t('view.settings.appearance.appearance.sort_instance_users_by') }}</span>
<el-radio-group :model-value="instanceUsersSortAlphabetical" @change="setInstanceUsersSortAlphabetical">
<el-radio :label="false">{{
t('view.settings.appearance.appearance.sort_instance_users_by_time')
}}</el-radio>
<el-radio :label="true">{{
t('view.settings.appearance.appearance.sort_instance_users_by_alphabet')
}}</el-radio>
</el-radio-group>
</div>
<div class="options-container-item">
<el-button size="small" :icon="Notebook" style="margin-right: 10px" @click="promptMaxTableSizeDialog">{{
t('view.settings.appearance.appearance.table_max_size')
}}</el-button>
</div>
<div class="options-container-item" />
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.appearance.timedate.header') }}</span>
<div class="options-container-item">
<span class="name">{{ t('view.settings.appearance.timedate.time_format') }}</span>
<el-radio-group
:model-value="dtHour12"
@change="
setDtHour12();
updateVRConfigVars();
">
<el-radio :label="true">{{ t('view.settings.appearance.timedate.time_format_12') }}</el-radio>
<el-radio :label="false">{{ t('view.settings.appearance.timedate.time_format_24') }}</el-radio>
</el-radio-group>
</div>
<simple-switch
:label="t('view.settings.appearance.timedate.force_iso_date_format')"
:value="dtIsoFormat"
@change="setDtIsoFormat" />
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.appearance.side_panel.header') }}</span>
<br />
<div class="options-container-item">
<span class="name">{{ t('view.settings.appearance.side_panel.sorting.header') }}</span>
<el-select
:model-value="sidebarSortMethod1"
style="width: 170px"
:placeholder="t('view.settings.appearance.side_panel.sorting.placeholder')"
@change="setSidebarSortMethod1($event)">
<el-option-group :label="t('view.settings.appearance.side_panel.sorting.dropdown_header')">
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.alphabetical')"
value="Sort Alphabetically"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.status')"
value="Sort by Status"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.private_to_bottom')"
value="Sort Private to Bottom"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.last_active')"
value="Sort by Last Active"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.last_seen')"
value="Sort by Last Seen"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.time_in_instance')"
value="Sort by Time in Instance"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.location')"
value="Sort by Location"></el-option>
</el-option-group>
</el-select>
<el-icon style="padding: 5px"><ArrowRight /></el-icon>
<el-select
:model-value="sidebarSortMethod2"
:disabled="!sidebarSortMethod1"
style="width: 170px"
clearable
:placeholder="t('view.settings.appearance.side_panel.sorting.placeholder')"
@change="setSidebarSortMethod2($event)">
<el-option-group :label="t('view.settings.appearance.side_panel.sorting.dropdown_header')">
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.alphabetical')"
value="Sort Alphabetically"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.status')"
value="Sort by Status"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.private_to_bottom')"
value="Sort Private to Bottom"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.last_active')"
value="Sort by Last Active"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.last_seen')"
value="Sort by Last Seen"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.time_in_instance')"
value="Sort by Time in Instance"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.location')"
value="Sort by Location"></el-option>
</el-option-group>
</el-select>
<el-icon style="padding: 5px"><ArrowRight /></el-icon>
<el-select
:model-value="sidebarSortMethod3"
:disabled="!sidebarSortMethod2"
style="width: 170px"
clearable
:placeholder="t('view.settings.appearance.side_panel.sorting.placeholder')"
@change="setSidebarSortMethod3($event)">
<el-option-group :label="t('view.settings.appearance.side_panel.sorting.dropdown_header')">
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.alphabetical')"
value="Sort Alphabetically"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.status')"
value="Sort by Status"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.private_to_bottom')"
value="Sort Private to Bottom"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.last_active')"
value="Sort by Last Active"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.last_seen')"
value="Sort by Last Seen"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.time_in_instance')"
value="Sort by Time in Instance"></el-option>
<el-option
class="x-friend-item"
:label="t('view.settings.appearance.side_panel.sorting.location')"
value="Sort by Location"></el-option>
</el-option-group>
</el-select>
</div>
<simple-switch
:label="t('view.settings.appearance.side_panel.group_by_instance')"
:value="isSidebarGroupByInstance"
:tooltip="t('view.settings.appearance.side_panel.group_by_instance_tooltip')"
@change="setIsSidebarGroupByInstance"></simple-switch>
<simple-switch
v-if="isSidebarGroupByInstance"
:label="t('view.settings.appearance.side_panel.hide_friends_in_same_instance')"
:value="isHideFriendsInSameInstance"
:tooltip="t('view.settings.appearance.side_panel.hide_friends_in_same_instance_tooltip')"
@change="setIsHideFriendsInSameInstance"></simple-switch>
<simple-switch
:label="t('view.settings.appearance.side_panel.split_favorite_friends')"
:value="isSidebarDivideByFriendGroup"
:tooltip="t('view.settings.appearance.side_panel.split_favorite_friends_tooltip')"
@change="setIsSidebarDivideByFriendGroup"></simple-switch>
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.appearance.user_dialog.header') }}</span>
<simple-switch
:label="t('view.settings.appearance.user_dialog.vrchat_notes')"
:value="!hideUserNotes"
@change="setHideUserNotes" />
<simple-switch
:label="t('view.settings.appearance.user_dialog.vrcx_memos')"
:value="!hideUserMemos"
@change="setHideUserMemos" />
<div class="options-container-item">
<span class="name">{{
t('view.settings.appearance.user_dialog.export_vrcx_memos_into_vrchat_notes')
}}</span>
<br />
<el-button size="small" :icon="DocumentCopy" style="margin-top: 5px" @click="redirectToToolsTab">{{
t('view.settings.appearance.user_dialog.export_notes')
}}</el-button>
</div>
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.appearance.friend_log.header') }}</span>
<simple-switch
:label="t('view.settings.appearance.friend_log.hide_unfriends')"
:value="hideUnfriends"
@change="setHideUnfriends"></simple-switch>
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.appearance.user_colors.header') }}</span>
<simple-switch
:label="t('view.settings.appearance.user_colors.random_colors_from_user_id')"
:value="randomUserColours"
@change="updateTrustColor('', '', true)"></simple-switch>
<div class="options-container-item">
<div>
<el-color-picker
:model-value="trustColor.untrusted"
size="small"
:predefine="['#CCCCCC']"
@change="updateTrustColor('untrusted', $event)">
</el-color-picker>
<span class="color-picker x-tag-untrusted">Visitor</span>
</div>
<div>
<el-color-picker
:model-value="trustColor.basic"
size="small"
:predefine="['#1778ff']"
@change="updateTrustColor('basic', $event)">
</el-color-picker>
<span class="color-picker x-tag-basic">New User</span>
</div>
<div>
<el-color-picker
:model-value="trustColor.known"
size="small"
:predefine="['#2bcf5c']"
@change="updateTrustColor('known', $event)">
</el-color-picker>
<span class="color-picker x-tag-known">User</span>
</div>
<div>
<el-color-picker
:model-value="trustColor.trusted"
size="small"
:predefine="['#ff7b42']"
@change="updateTrustColor('trusted', $event)">
</el-color-picker>
<span class="color-picker x-tag-trusted">Known User</span>
</div>
<div>
<el-color-picker
:model-value="trustColor.veteran"
size="small"
:predefine="['#b18fff', '#8143e6', '#ff69b4', '#b52626', '#ffd000', '#abcdef']"
@change="updateTrustColor('veteran', $event)">
</el-color-picker>
<span class="color-picker x-tag-veteran">Trusted User</span>
</div>
<div>
<el-color-picker
:model-value="trustColor.vip"
size="small"
:predefine="['#ff2626']"
@change="updateTrustColor('vip', $event)">
</el-color-picker>
<span class="color-picker x-tag-vip">VRChat Team</span>
</div>
<div>
<el-color-picker
:model-value="trustColor.troll"
size="small"
:predefine="['#782f2f']"
@change="updateTrustColor('troll', $event)">
</el-color-picker>
<span class="color-picker x-tag-troll">Nuisance</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ArrowDown, ArrowRight, DocumentCopy, Notebook } from '@element-plus/icons-vue';
import { computed, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useAppearanceSettingsStore, useFavoriteStore, useVrStore } from '../../../../stores';
import { THEME_CONFIG } from '../../../../shared/constants';
import { redirectToToolsTab } from '../../../../shared/utils/base/ui';
import SimpleSwitch from '../SimpleSwitch.vue';
const { messages, t } = useI18n();
const appearanceSettingsStore = useAppearanceSettingsStore();
const { saveOpenVROption, updateVRConfigVars } = useVrStore();
const {
appLanguage,
themeMode,
displayVRCPlusIconsAsAvatar,
hideNicknames,
isAgeGatedInstancesVisible,
sortFavorites,
instanceUsersSortAlphabetical,
dtHour12,
dtIsoFormat,
sidebarSortMethod1,
sidebarSortMethod2,
sidebarSortMethod3,
isSidebarGroupByInstance,
isHideFriendsInSameInstance,
isSidebarDivideByFriendGroup,
hideUserNotes,
hideUserMemos,
hideUnfriends,
randomUserColours,
trustColor
} = storeToRefs(appearanceSettingsStore);
const { saveSortFavoritesOption } = useFavoriteStore();
const {
setDisplayVRCPlusIconsAsAvatar,
setHideNicknames,
setIsAgeGatedInstancesVisible,
setInstanceUsersSortAlphabetical,
setDtHour12,
setDtIsoFormat,
setSidebarSortMethod1,
setSidebarSortMethod2,
setSidebarSortMethod3,
setIsSidebarGroupByInstance,
setIsHideFriendsInSameInstance,
setIsSidebarDivideByFriendGroup,
setHideUserNotes,
setHideUserMemos,
setHideUnfriends,
updateTrustColor,
saveThemeMode,
changeAppLanguage,
promptMaxTableSizeDialog
} = appearanceSettingsStore;
const zoomLevel = ref(100);
const isLinux = computed(() => LINUX);
initGetZoomLevel();
async function initGetZoomLevel() {
addEventListener('wheel', (event) => {
if (event.ctrlKey) {
getZoomLevel();
}
});
getZoomLevel();
}
async function getZoomLevel() {
zoomLevel.value = ((await AppApi.GetZoom()) + 10) * 10;
}
function setZoomLevel() {
AppApi.SetZoom(zoomLevel.value / 10 - 10);
}
</script>

View File

@@ -79,9 +79,9 @@
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useAdvancedSettingsStore, useDiscordPresenceSettingsStore } from '../../../stores';
import { useAdvancedSettingsStore, useDiscordPresenceSettingsStore } from '../../../../stores';
import SimpleSwitch from './SimpleSwitch.vue';
import SimpleSwitch from '../SimpleSwitch.vue';
const { t } = useI18n();

View File

@@ -0,0 +1,375 @@
<template>
<div>
<div class="options-container" style="margin-top: 0">
<span class="header">{{ t('view.settings.general.general.header') }}</span>
<div class="x-friend-list" style="margin-top: 10px">
<div class="x-friend-item" style="cursor: default">
<div class="detail">
<span class="name">{{ t('view.settings.general.general.version') }}</span>
<span class="extra" v-text="appVersion"></span>
</div>
</div>
<div class="x-friend-item" @click="checkForVRCXUpdate">
<div class="detail">
<span class="name">{{ t('view.settings.general.general.latest_app_version') }}</span>
<span v-if="latestAppVersion" class="extra" v-text="latestAppVersion"></span>
<span v-else class="extra">{{
t('view.settings.general.general.latest_app_version_refresh')
}}</span>
</div>
</div>
<div class="x-friend-item" @click="openExternalLink('https://github.com/vrcx-team/VRCX')">
<div class="detail">
<span class="name">{{ t('view.settings.general.general.repository_url') }}</span>
<span v-once class="extra">https://github.com/vrcx-team/VRCX</span>
</div>
</div>
<div class="x-friend-item" @click="openExternalLink('https://vrcx.app/discord')">
<div class="detail">
<span class="name">{{ t('view.settings.general.general.support') }}</span>
<span v-once class="extra">https://vrcx.app/discord</span>
</div>
</div>
</div>
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.general.vrcx_updater.header') }}</span>
<div class="options-container-item">
<el-button size="small" :icon="Document" @click="showChangeLogDialog">{{
t('view.settings.general.vrcx_updater.change_log')
}}</el-button>
<el-button size="small" :icon="Upload" @click="showVRCXUpdateDialog()">{{
t('view.settings.general.vrcx_updater.change_build')
}}</el-button>
</div>
<div class="options-container-item">
<span class="name">{{ t('view.settings.general.vrcx_updater.update_action') }}</span>
<br />
<el-radio-group
:model-value="autoUpdateVRCX"
size="small"
style="margin-top: 5px"
@change="setAutoUpdateVRCX">
<el-radio-button label="Off">{{
t('view.settings.general.vrcx_updater.auto_update_off')
}}</el-radio-button>
<el-radio-button label="Notify">{{
t('view.settings.general.vrcx_updater.auto_update_notify')
}}</el-radio-button>
<el-radio-button label="Auto Download">{{
t('view.settings.general.vrcx_updater.auto_update_download')
}}</el-radio-button>
</el-radio-group>
</div>
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.general.application.header') }}</span>
<simple-switch
v-if="!isLinux"
:label="t('view.settings.general.application.startup')"
:value="isStartAtWindowsStartup"
@change="setIsStartAtWindowsStartup" />
<simple-switch
v-if="!isLinux"
:label="t('view.settings.general.application.minimized')"
:value="isStartAsMinimizedState"
@change="setIsStartAsMinimizedState" />
<simple-switch
v-else
:label="t('view.settings.general.application.minimized')"
:value="isStartAsMinimizedState"
:tooltip="t('view.settings.general.application.startup_linux')"
@change="setIsStartAsMinimizedState" />
<simple-switch
:label="t('view.settings.general.application.tray')"
:value="isCloseToTray"
@change="setIsCloseToTray" />
<simple-switch
v-if="!isLinux"
:label="t('view.settings.general.application.disable_gpu_acceleration')"
:value="disableGpuAcceleration"
:tooltip="t('view.settings.general.application.disable_gpu_acceleration_tooltip')"
@change="setDisableGpuAcceleration" />
<simple-switch
v-if="!isLinux"
:label="t('view.settings.general.application.disable_vr_overlay_gpu_acceleration')"
:value="disableVrOverlayGpuAcceleration"
:tooltip="t('view.settings.general.application.disable_gpu_acceleration_tooltip')"
@change="setDisableVrOverlayGpuAcceleration" />
<div class="options-container-item">
<el-button size="small" :icon="Connection" @click="promptProxySettings">{{
t('view.settings.general.application.proxy')
}}</el-button>
</div>
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.general.favorites.header') }}</span>
<br />
<el-select
:model-value="localFavoriteFriendsGroups"
multiple
clearable
:placeholder="t('view.settings.general.favorites.group_placeholder')"
style="margin-top: 8px"
@change="setLocalFavoriteFriendsGroups">
<el-option-group :label="t('view.settings.general.favorites.group_placeholder')">
<el-option
v-for="group in favoriteFriendGroups"
:key="group.key"
:label="group.displayName"
:value="group.key"
class="x-friend-item">
<div class="detail">
<span class="name" v-text="group.displayName"></span>
</div>
</el-option>
</el-option-group>
</el-select>
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.general.logging.header') }}</span>
<simple-switch
:label="t('view.settings.advanced.advanced.cache_debug.udon_exception_logging')"
:value="udonExceptionLogging"
@change="
setUdonExceptionLogging();
saveOpenVROption();
" />
<simple-switch
:label="t('view.settings.general.logging.resource_load')"
:value="logResourceLoad"
@change="setLogResourceLoad" />
<simple-switch
:label="t('view.settings.general.logging.empty_avatar')"
:value="logEmptyAvatars"
@change="setLogEmptyAvatars" />
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.general.automation.header') }}</span>
<simple-switch
:label="t('view.settings.general.automation.auto_change_status')"
:value="autoStateChangeEnabled"
:tooltip="t('view.settings.general.automation.auto_state_change_tooltip')"
@change="setAutoStateChangeEnabled" />
<div class="options-container-item">
<span class="name">{{ t('view.settings.general.automation.alone_status') }}</span>
<el-select
:model-value="autoStateChangeAloneStatus"
:disabled="!autoStateChangeEnabled"
style="margin-top: 8px"
size="small"
@change="setAutoStateChangeAloneStatus">
<el-option :label="t('dialog.user.status.join_me')" value="join me">
<i class="x-user-status joinme"></i> {{ t('dialog.user.status.join_me') }}
</el-option>
<el-option :label="t('dialog.user.status.online')" value="active">
<i class="x-user-status online"></i> {{ t('dialog.user.status.online') }}
</el-option>
<el-option :label="t('dialog.user.status.ask_me')" value="ask me">
<i class="x-user-status askme"></i> {{ t('dialog.user.status.ask_me') }}
</el-option>
<el-option :label="t('dialog.user.status.busy')" value="busy">
<i class="x-user-status busy"></i> {{ t('dialog.user.status.busy') }}
</el-option>
</el-select>
</div>
<div class="options-container-item">
<span class="name">{{ t('view.settings.general.automation.company_status') }}</span>
<el-select
:model-value="autoStateChangeCompanyStatus"
:disabled="!autoStateChangeEnabled"
style="margin-top: 8px"
size="small"
@change="setAutoStateChangeCompanyStatus">
<el-option :label="t('dialog.user.status.join_me')" value="join me">
<i class="x-user-status joinme"></i> {{ t('dialog.user.status.join_me') }}
</el-option>
<el-option :label="t('dialog.user.status.online')" value="active">
<i class="x-user-status online"></i> {{ t('dialog.user.status.online') }}
</el-option>
<el-option :label="t('dialog.user.status.ask_me')" value="ask me">
<i class="x-user-status askme"></i> {{ t('dialog.user.status.ask_me') }}
</el-option>
<el-option :label="t('dialog.user.status.busy')" value="busy">
<i class="x-user-status busy"></i> {{ t('dialog.user.status.busy') }}
</el-option>
</el-select>
</div>
<div class="options-container-item">
<span class="name">{{ t('view.settings.general.automation.allowed_instance_types') }}</span>
<el-select
:model-value="autoStateChangeInstanceTypes"
:disabled="!autoStateChangeEnabled"
multiple
clearable
:placeholder="t('view.settings.general.automation.instance_type_placeholder')"
style="margin-top: 8px"
size="small"
@change="setAutoStateChangeInstanceTypes">
<el-option-group :label="t('view.settings.general.automation.allowed_instance_types')">
<el-option
v-for="instanceType in instanceTypes"
:key="instanceType"
:label="instanceType"
:value="instanceType"
class="x-friend-item">
<div class="detail">
<span class="name" v-text="instanceType"></span>
</div>
</el-option>
</el-option-group>
</el-select>
</div>
<div class="options-container-item">
<span class="name">{{ t('view.settings.general.automation.alone_condition') }}</span>
<el-radio-group
:model-value="autoStateChangeNoFriends"
:disabled="!autoStateChangeEnabled"
@change="setAutoStateChangeNoFriends">
<el-radio :label="false">{{ t('view.settings.general.automation.alone') }}</el-radio>
<el-radio :label="true">{{ t('view.settings.general.automation.no_friends') }}</el-radio>
</el-radio-group>
</div>
<div class="options-container-item">
<span class="name"
>{{ t('view.settings.general.automation.auto_invite_request_accept') }}
<el-tooltip
placement="top"
style="margin-left: 5px"
:content="t('view.settings.general.automation.auto_invite_request_accept_tooltip')">
<el-icon><InfoFilled /></el-icon>
</el-tooltip>
</span>
<br />
<el-radio-group
:model-value="autoAcceptInviteRequests"
size="small"
style="margin-top: 5px"
@change="setAutoAcceptInviteRequests">
<el-radio-button label="Off">{{
t('view.settings.general.automation.auto_invite_request_accept_off')
}}</el-radio-button>
<el-radio-button label="All Favorites">{{
t('view.settings.general.automation.auto_invite_request_accept_favs')
}}</el-radio-button>
<el-radio-button label="Selected Favorites">{{
t('view.settings.general.automation.auto_invite_request_accept_selected_favs')
}}</el-radio-button>
</el-radio-group>
</div>
</div>
<div class="options-container">
<span class="header">{{ t('view.settings.general.contributors.header') }}</span>
<div class="options-container-item">
<img
src="https://contrib.rocks/image?repo=vrcx-team/VRCX"
alt="Contributors"
style="cursor: pointer"
@click="openExternalLink('https://github.com/vrcx-team/VRCX/graphs/contributors')" />
</div>
</div>
<div class="options-container" style="margin-top: 45px; border-top: 1px solid #eee; padding-top: 30px">
<span class="header">{{ t('view.settings.general.legal_notice.header') }}</span>
<div class="options-container-item">
<p>
&copy; 2019-2025
<a class="x-link" @click="openExternalLink('https://github.com/pypy-vrc')">pypy</a> &amp;
<a class="x-link" @click="openExternalLink('https://github.com/Natsumi-sama')">Natsumi</a>
</p>
<p>{{ t('view.settings.general.legal_notice.info') }}</p>
<p>{{ t('view.settings.general.legal_notice.disclaimer1') }}</p>
<p>{{ t('view.settings.general.legal_notice.disclaimer2') }}</p>
</div>
<div class="options-container-item">
<el-button size="small" @click="openOSSDialog">{{
t('view.settings.general.legal_notice.open_source_software_notice')
}}</el-button>
</div>
</div>
<OpenSourceSoftwareNoticeDialog v-if="ossDialog" v-model:ossDialog="ossDialog" />
</div>
</template>
<script setup>
import { Connection, Document, InfoFilled, Upload } from '@element-plus/icons-vue';
import { computed, defineAsyncComponent, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useFavoriteStore, useGeneralSettingsStore, useVRCXUpdaterStore, useVrStore } from '../../../../stores';
import { openExternalLink } from '../../../../shared/utils';
import SimpleSwitch from '../SimpleSwitch.vue';
const { t } = useI18n();
const generalSettingsStore = useGeneralSettingsStore();
const vrcxUpdaterStore = useVRCXUpdaterStore();
const favoriteStore = useFavoriteStore();
const { saveOpenVROption } = useVrStore();
const {
isStartAtWindowsStartup,
isStartAsMinimizedState,
isCloseToTray,
disableGpuAcceleration,
disableVrOverlayGpuAcceleration,
localFavoriteFriendsGroups,
udonExceptionLogging,
logResourceLoad,
logEmptyAvatars,
autoStateChangeEnabled,
autoStateChangeAloneStatus,
autoStateChangeCompanyStatus,
autoStateChangeInstanceTypes,
autoStateChangeNoFriends,
autoAcceptInviteRequests
} = storeToRefs(generalSettingsStore);
const {
setIsStartAtWindowsStartup,
setIsStartAsMinimizedState,
setIsCloseToTray,
setDisableGpuAcceleration,
setDisableVrOverlayGpuAcceleration,
setUdonExceptionLogging,
setLogResourceLoad,
setLogEmptyAvatars,
setAutoStateChangeEnabled,
setAutoStateChangeAloneStatus,
setAutoStateChangeCompanyStatus,
setAutoStateChangeInstanceTypes,
setAutoStateChangeNoFriends,
setAutoAcceptInviteRequests,
setLocalFavoriteFriendsGroups,
promptProxySettings
} = generalSettingsStore;
const { favoriteFriendGroups } = storeToRefs(favoriteStore);
const { appVersion, autoUpdateVRCX, latestAppVersion } = storeToRefs(vrcxUpdaterStore);
const { setAutoUpdateVRCX, checkForVRCXUpdate, showVRCXUpdateDialog, showChangeLogDialog } = vrcxUpdaterStore;
const instanceTypes = ref([
'invite',
'invite+',
'friends',
'friends+',
'public',
'groupPublic',
'groupPlus',
'groupOnly'
]);
const ossDialog = ref(false);
const isLinux = computed(() => LINUX);
const OpenSourceSoftwareNoticeDialog = defineAsyncComponent(
() => import('../../dialogs/OpenSourceSoftwareNoticeDialog.vue')
);
function openOSSDialog() {
ossDialog.value = true;
}
</script>

View File

@@ -0,0 +1,347 @@
<template>
<div>
<div class="options-container" style="margin-top: 0">
<span class="header">{{ t('view.settings.notifications.notifications.header') }}</span>
<div class="options-container-item">
<el-button size="small" :icon="ChatSquare" @click="showNotyFeedFiltersDialog">{{
t('view.settings.notifications.notifications.notification_filter')
}}</el-button>
</div>
</div>
<div class="options-container">
<span class="sub-header">{{
t('view.settings.notifications.notifications.steamvr_notifications.header')
}}</span>
<div class="options-container-item">
<span class="name">{{
t('view.settings.notifications.notifications.desktop_notifications.when_to_display')
}}</span>
<br />
<el-radio-group
:model-value="overlayToast"
size="small"
:disabled="
(!overlayNotifications || !openVR) &&
!xsNotifications &&
!ovrtHudNotifications &&
!ovrtWristNotifications
"
style="margin-top: 5px"
@change="
setOverlayToast($event);
saveOpenVROption();
">
<el-radio-button label="Never">{{
t('view.settings.notifications.notifications.conditions.never')
}}</el-radio-button>
<el-radio-button label="Game Running">{{
t('view.settings.notifications.notifications.conditions.inside_vrchat')
}}</el-radio-button>
<el-radio-button label="Game Closed">{{
t('view.settings.notifications.notifications.conditions.outside_vrchat')
}}</el-radio-button>
<el-radio-button label="Always">{{
t('view.settings.notifications.notifications.conditions.always')
}}</el-radio-button>
</el-radio-group>
</div>
<simple-switch
:label="t('view.settings.notifications.notifications.steamvr_notifications.steamvr_overlay')"
:value="openVR"
@change="
setOpenVR();
saveOpenVROption();
" />
<template v-if="openVR">
<simple-switch
:label="t('view.settings.notifications.notifications.steamvr_notifications.overlay_notifications')"
:value="overlayNotifications"
:disabled="!openVR"
@change="
setOverlayNotifications();
saveOpenVROption();
" />
<div class="options-container-item">
<el-button
size="small"
:icon="Rank"
:disabled="!overlayNotifications || !openVR"
@click="showNotificationPositionDialog"
>{{
t('view.settings.notifications.notifications.steamvr_notifications.notification_position')
}}</el-button
>
</div>
<div class="options-container-item">
<span class="name" style="vertical-align: top; padding-top: 10px">{{
t('view.settings.notifications.notifications.steamvr_notifications.notification_opacity')
}}</span>
<el-slider
:model-value="notificationOpacity"
@input="setNotificationOpacity"
:min="0"
:max="100"
style="display: inline-block; width: 300px; padding-top: 16px" />
</div>
<div class="options-container-item">
<el-button
size="small"
:icon="Timer"
:disabled="(!overlayNotifications || !openVR) && !xsNotifications"
@click="promptNotificationTimeout"
>{{
t('view.settings.notifications.notifications.steamvr_notifications.notification_timeout')
}}</el-button
>
</div>
<simple-switch
:label="t('view.settings.notifications.notifications.steamvr_notifications.user_images')"
:value="imageNotifications"
@change="
setImageNotifications();
saveOpenVROption();
" />
</template>
<template v-if="!isLinux">
<simple-switch
:label="
t('view.settings.notifications.notifications.steamvr_notifications.xsoverlay_notifications')
"
:value="xsNotifications"
@change="
setXsNotifications();
saveOpenVROption();
" />
</template>
<template v-else>
<simple-switch
:label="
t('view.settings.notifications.notifications.steamvr_notifications.wlxoverlay_notifications')
"
:value="xsNotifications"
@change="
setXsNotifications();
saveOpenVROption();
" />
</template>
<template v-if="!isLinux">
<simple-switch
:label="
t(
'view.settings.notifications.notifications.steamvr_notifications.ovrtoolkit_hud_notifications'
)
"
:value="ovrtHudNotifications"
@change="
setOvrtHudNotifications();
saveOpenVROption();
" />
<simple-switch
:label="
t(
'view.settings.notifications.notifications.steamvr_notifications.ovrtoolkit_wrist_notifications'
)
"
:value="ovrtWristNotifications"
@change="
setOvrtWristNotifications();
saveOpenVROption();
" />
</template>
</div>
<div class="options-container">
<span class="sub-header">{{
t('view.settings.notifications.notifications.desktop_notifications.header')
}}</span>
<div class="options-container-item">
<span class="name">{{
t('view.settings.notifications.notifications.desktop_notifications.when_to_display')
}}</span>
<br />
<el-radio-group
:model-value="desktopToast"
size="small"
style="margin-top: 5px"
@change="
setDesktopToast($event);
saveOpenVROption();
">
<el-radio-button label="Never">{{
t('view.settings.notifications.notifications.conditions.never')
}}</el-radio-button>
<el-radio-button label="Desktop Mode">{{
t('view.settings.notifications.notifications.conditions.desktop')
}}</el-radio-button>
<el-radio-button label="Inside VR">{{
t('view.settings.notifications.notifications.conditions.inside_vr')
}}</el-radio-button>
<el-radio-button label="Outside VR">{{
t('view.settings.notifications.notifications.conditions.outside_vr')
}}</el-radio-button>
<el-radio-button label="Game Running">{{
t('view.settings.notifications.notifications.conditions.inside_vrchat')
}}</el-radio-button>
<el-radio-button label="Game Closed">{{
t('view.settings.notifications.notifications.conditions.outside_vrchat')
}}</el-radio-button>
<el-radio-button label="Always">{{
t('view.settings.notifications.notifications.conditions.always')
}}</el-radio-button>
</el-radio-group>
</div>
<simple-switch
:label="
t('view.settings.notifications.notifications.desktop_notifications.desktop_notification_while_afk')
"
:value="afkDesktopToast"
@change="
setAfkDesktopToast();
saveOpenVROption();
" />
</div>
<div class="options-container">
<span class="sub-header">{{ t('view.settings.notifications.notifications.text_to_speech.header') }}</span>
<div class="options-container-item">
<span class="name">{{
t('view.settings.notifications.notifications.text_to_speech.when_to_play')
}}</span>
<br />
<el-radio-group
:model-value="notificationTTS"
size="small"
style="margin-top: 5px"
@change="saveNotificationTTS">
<el-radio-button label="Never">{{
t('view.settings.notifications.notifications.conditions.never')
}}</el-radio-button>
<el-radio-button label="Inside VR">{{
t('view.settings.notifications.notifications.conditions.inside_vr')
}}</el-radio-button>
<el-radio-button label="Game Running">{{
t('view.settings.notifications.notifications.conditions.inside_vrchat')
}}</el-radio-button>
<el-radio-button label="Game Closed">{{
t('view.settings.notifications.notifications.conditions.outside_vrchat')
}}</el-radio-button>
<el-radio-button label="Always">{{
t('view.settings.notifications.notifications.conditions.always')
}}</el-radio-button>
</el-radio-group>
</div>
<div class="options-container-item">
<span class="name">{{ t('view.settings.notifications.notifications.text_to_speech.tts_voice') }}</span>
<el-dropdown trigger="click" size="small" @command="(voice) => changeTTSVoice(voice)">
<el-button size="small" :disabled="notificationTTS === 'Never'">
<span
>{{ getTTSVoiceName() }} <el-icon style="margin-left: 5px"><ArrowDown /></el-icon
></span>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="(voice, index) in TTSvoices"
:key="index"
:command="index"
v-text="voice.name" />
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<simple-switch
:label="t('view.settings.notifications.notifications.text_to_speech.use_memo_nicknames')"
:value="notificationTTSNickName"
:disabled="notificationTTS === 'Never'"
@change="
setNotificationTTSNickName();
saveOpenVROption();
" />
<simple-switch
:label="t('view.settings.notifications.notifications.text_to_speech.tts_test_placeholder')"
:value="isTestTTSVisible"
@change="isTestTTSVisible = !isTestTTSVisible" />
<div v-if="isTestTTSVisible" style="margin-top: 5px">
<el-input
v-model="notificationTTSTest"
type="textarea"
:placeholder="t('view.settings.notifications.notifications.text_to_speech.tts_test_placeholder')"
:rows="1"
style="width: 175px; display: inline-block"></el-input>
<el-button size="small" :icon="VideoPlay" style="margin-left: 10px" @click="testNotificationTTS">{{
t('view.settings.notifications.notifications.text_to_speech.play')
}}</el-button>
</div>
</div>
<NotificationPositionDialog v-model:isNotificationPositionDialogVisible="isNotificationPositionDialogVisible" />
<FeedFiltersDialog v-model:feedFiltersDialogMode="feedFiltersDialogMode" />
</div>
</template>
<script setup>
import { ArrowDown, ChatSquare, Rank, Timer, VideoPlay } from '@element-plus/icons-vue';
import { computed, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useAdvancedSettingsStore, useNotificationsSettingsStore, useVrStore } from '../../../../stores';
import FeedFiltersDialog from '../../dialogs/FeedFiltersDialog.vue';
import NotificationPositionDialog from '../../dialogs/NotificationPositionDialog.vue';
import SimpleSwitch from '../SimpleSwitch.vue';
const { t } = useI18n();
const notificationsSettingsStore = useNotificationsSettingsStore();
const advancedSettingsStore = useAdvancedSettingsStore();
const { saveOpenVROption } = useVrStore();
const {
overlayToast,
openVR,
overlayNotifications,
xsNotifications,
ovrtHudNotifications,
ovrtWristNotifications,
imageNotifications,
desktopToast,
afkDesktopToast,
notificationTTS,
notificationTTSNickName,
isTestTTSVisible,
notificationTTSTest,
TTSvoices
} = storeToRefs(notificationsSettingsStore);
const { notificationOpacity } = storeToRefs(advancedSettingsStore);
const {
setOverlayToast,
setOpenVR,
setOverlayNotifications,
setXsNotifications,
setOvrtHudNotifications,
setOvrtWristNotifications,
setImageNotifications,
setDesktopToast,
setAfkDesktopToast,
setNotificationTTSNickName,
getTTSVoiceName,
changeTTSVoice,
saveNotificationTTS,
testNotificationTTS,
promptNotificationTimeout
} = notificationsSettingsStore;
const { setNotificationOpacity } = advancedSettingsStore;
const feedFiltersDialogMode = ref('');
const isNotificationPositionDialogVisible = ref(false);
const isLinux = computed(() => LINUX);
function showNotyFeedFiltersDialog() {
feedFiltersDialogMode.value = 'noty';
}
function showNotificationPositionDialog() {
isNotificationPositionDialogVisible.value = true;
}
</script>

View File

@@ -132,10 +132,10 @@
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { redirectToToolsTab } from '../../../shared/utils/base/ui';
import { useAdvancedSettingsStore } from '../../../stores';
import { redirectToToolsTab } from '../../../../shared/utils/base/ui';
import { useAdvancedSettingsStore } from '../../../../stores';
import SimpleSwitch from './SimpleSwitch.vue';
import SimpleSwitch from '../SimpleSwitch.vue';
const { t } = useI18n();

View File

@@ -0,0 +1,19 @@
<template>
<div>
<WristOverlaySettings @open-feed-filters="showWristFeedFiltersDialog" />
<FeedFiltersDialog v-model:feedFiltersDialogMode="feedFiltersDialogMode" />
</div>
</template>
<script setup>
import { ref } from 'vue';
import FeedFiltersDialog from '../../dialogs/FeedFiltersDialog.vue';
import WristOverlaySettings from '../WristOverlaySettings.vue';
const feedFiltersDialogMode = ref('');
function showWristFeedFiltersDialog() {
feedFiltersDialogMode.value = 'wrist';
}
</script>