mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-23 00:33:50 +02:00
503 lines
19 KiB
Vue
503 lines
19 KiB
Vue
<template>
|
|
<Dialog :open="isVRChatConfigDialogVisible" @update:open="(open) => (open ? null : closeDialog())">
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>{{ t('dialog.config_json.header') }}</DialogTitle>
|
|
</DialogHeader>
|
|
<div>
|
|
<div class="text-xs break-keep">
|
|
{{ t('dialog.config_json.description1') }} <br />
|
|
{{ t('dialog.config_json.description2') }}
|
|
</div>
|
|
<br />
|
|
<span class="mr-1.5">{{ t('dialog.config_json.cache_size') }}</span>
|
|
<span v-text="VRChatUsedCacheSize"></span>
|
|
<span>/</span>
|
|
<span v-text="totalCacheSize"></span>
|
|
<span>GB</span>
|
|
<TooltipWrapper side="top" :content="t('dialog.config_json.refresh')">
|
|
<Button
|
|
class="rounded-full ml-1.5"
|
|
variant="outline"
|
|
size="icon-sm"
|
|
:disabled="VRChatCacheSizeLoading"
|
|
@click="getVRChatCacheSize">
|
|
<Spinner v-if="VRChatCacheSizeLoading" />
|
|
<RefreshCw v-else />
|
|
</Button>
|
|
</TooltipWrapper>
|
|
|
|
<div class="mt-2">
|
|
<span class="mr-1.5">{{ t('dialog.config_json.delete_all_cache') }}</span>
|
|
<Button class="ml-1.5" size="sm" variant="outline" @click="showDeleteAllVRChatCacheConfirm">{{
|
|
t('dialog.config_json.delete_cache')
|
|
}}</Button>
|
|
</div>
|
|
|
|
<div class="mt-2">
|
|
<span class="mr-1.5">{{ t('dialog.config_json.delete_old_cache') }}</span>
|
|
<Button class="ml-1.5" size="sm" variant="outline" @click="sweepVRChatCache">{{
|
|
t('dialog.config_json.sweep_cache')
|
|
}}</Button>
|
|
</div>
|
|
|
|
<div class="mt-2" v-for="(item, value) in VRChatConfigList" :key="value" style="display: block">
|
|
<span style="word-break: keep-all">{{ item.name }}:</span>
|
|
<div style="display: flex">
|
|
<InputGroupAction
|
|
class="mt-1.5"
|
|
v-model="VRChatConfigFile[value]"
|
|
:placeholder="item.default"
|
|
size="sm"
|
|
:type="item.type ? item.type : 'text'"
|
|
:min="item.min"
|
|
:max="item.max"
|
|
@input="refreshDialogValues"
|
|
style="flex: 1">
|
|
<template #actions>
|
|
<Button
|
|
size="icon-sm"
|
|
variant="outline"
|
|
v-if="item.folderBrowser"
|
|
@click="openConfigFolderBrowser(value)">
|
|
<FolderOpen />
|
|
</Button>
|
|
</template>
|
|
</InputGroupAction>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display: inline-block; margin-top: 8px">
|
|
<span>{{ t('dialog.config_json.camera_resolution') }}</span>
|
|
<br />
|
|
<Select
|
|
:model-value="vrchatCameraResolutionKey"
|
|
@update:modelValue="(v) => (vrchatCameraResolutionKey = v)">
|
|
<SelectTrigger size="sm" style="margin-top: 6px">
|
|
<SelectValue :placeholder="getVRChatCameraResolution()" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectGroup>
|
|
<SelectItem
|
|
v-for="row in VRChatCameraResolutions"
|
|
:key="row.name"
|
|
:value="getVRChatResolutionKey(row)">
|
|
{{ row.name }}
|
|
</SelectItem>
|
|
</SelectGroup>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<br />
|
|
|
|
<div style="display: inline-block; margin-top: 8px">
|
|
<span>{{ t('dialog.config_json.spout_resolution') }}</span>
|
|
<br />
|
|
<Select
|
|
:model-value="vrchatSpoutResolutionKey"
|
|
@update:modelValue="(v) => (vrchatSpoutResolutionKey = v)">
|
|
<SelectTrigger size="sm" style="margin-top: 6px">
|
|
<SelectValue :placeholder="getVRChatSpoutResolution()" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectGroup>
|
|
<SelectItem
|
|
v-for="row in VRChatScreenshotResolutions"
|
|
:key="row.name"
|
|
:value="getVRChatResolutionKey(row)">
|
|
{{ row.name }}
|
|
</SelectItem>
|
|
</SelectGroup>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<br />
|
|
|
|
<div style="display: inline-block; margin-top: 8px">
|
|
<span>{{ t('dialog.config_json.screenshot_resolution') }}</span>
|
|
<br />
|
|
<Select
|
|
:model-value="vrchatScreenshotResolutionKey"
|
|
@update:modelValue="(v) => (vrchatScreenshotResolutionKey = v)">
|
|
<SelectTrigger size="sm" style="margin-top: 6px">
|
|
<SelectValue :placeholder="getVRChatScreenshotResolution()" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectGroup>
|
|
<SelectItem
|
|
v-for="row in VRChatScreenshotResolutions"
|
|
:key="row.name"
|
|
:value="getVRChatResolutionKey(row)">
|
|
{{ row.name }}
|
|
</SelectItem>
|
|
</SelectGroup>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<br />
|
|
|
|
<label class="inline-flex items-center gap-2" style="margin-top: 6px; display: block">
|
|
<Checkbox
|
|
v-model="VRChatConfigFile.picture_output_split_by_date"
|
|
@update:modelValue="refreshDialogValues" />
|
|
<span>{{ t('dialog.config_json.picture_sort_by_date') }}</span>
|
|
</label>
|
|
<label class="inline-flex items-center gap-2" style="margin-top: 6px; display: block">
|
|
<Checkbox v-model="VRChatConfigFile.disableRichPresence" @update:modelValue="refreshDialogValues" />
|
|
<span>{{ t('dialog.config_json.disable_discord_presence') }}</span>
|
|
</label>
|
|
</div>
|
|
<DialogFooter>
|
|
<div style="display: flex; align-items: center; justify-content: space-between" class="w-full">
|
|
<div>
|
|
<Button
|
|
variant="outline"
|
|
@click="openExternalLink('https://docs.vrchat.com/docs/configuration-file')"
|
|
>{{ t('dialog.config_json.vrchat_docs') }}</Button
|
|
>
|
|
</div>
|
|
<div>
|
|
<Button variant="secondary" class="mr-2" @click="closeDialog">{{
|
|
t('dialog.config_json.cancel')
|
|
}}</Button>
|
|
<Button :disabled="loading" @click="saveVRChatConfigFile">{{
|
|
t('dialog.config_json.save')
|
|
}}</Button>
|
|
</div>
|
|
</div>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
|
import { computed, ref, watch } from 'vue';
|
|
import { FolderOpen, RefreshCw } from 'lucide-vue-next';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import { InputGroupAction } from '@/components/ui/input-group';
|
|
import { Spinner } from '@/components/ui/spinner';
|
|
import { storeToRefs } from 'pinia';
|
|
import { toast } from 'vue-sonner';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
import { useAdvancedSettingsStore, useGameStore, useModalStore } from '../../../stores';
|
|
import { VRChatCameraResolutions, VRChatScreenshotResolutions } from '../../../shared/constants';
|
|
import { getVRChatResolution, openExternalLink } from '../../../shared/utils';
|
|
|
|
const { VRChatUsedCacheSize, VRChatTotalCacheSize, VRChatCacheSizeLoading } = storeToRefs(useGameStore());
|
|
const { sweepVRChatCache, getVRChatCacheSize } = useGameStore();
|
|
const { folderSelectorDialog } = useAdvancedSettingsStore();
|
|
const { isVRChatConfigDialogVisible } = storeToRefs(useAdvancedSettingsStore());
|
|
const modalStore = useModalStore();
|
|
|
|
const { t } = useI18n();
|
|
|
|
const VRChatConfigFile = ref(/** @type {Record<string, any>} */ ({}));
|
|
// it's a object
|
|
const VRChatConfigList = ref(
|
|
/** @type {Record<string, any>} */ ({
|
|
cache_size: {
|
|
name: t('dialog.config_json.max_cache_size'),
|
|
default: '30',
|
|
type: 'number',
|
|
min: 30
|
|
},
|
|
cache_expiry_delay: {
|
|
name: t('dialog.config_json.cache_expiry_delay'),
|
|
default: '30',
|
|
type: 'number',
|
|
min: 30
|
|
},
|
|
cache_directory: {
|
|
name: t('dialog.config_json.cache_directory'),
|
|
default: '%AppData%\\..\\LocalLow\\VRChat\\VRChat',
|
|
folderBrowser: true
|
|
},
|
|
picture_output_folder: {
|
|
name: t('dialog.config_json.picture_directory'),
|
|
// my pictures folder
|
|
default: `%UserProfile%\\Pictures\\VRChat`,
|
|
folderBrowser: true
|
|
},
|
|
// dynamic_bone_max_affected_transform_count: {
|
|
// name: 'Dynamic Bones Limit Max Transforms (0 disable all transforms)',
|
|
// default: '32',
|
|
// type: 'number',
|
|
// min: 0
|
|
// },
|
|
// dynamic_bone_max_collider_check_count: {
|
|
// name: 'Dynamic Bones Limit Max Collider Collisions (0 disable all colliders)',
|
|
// default: '8',
|
|
// type: 'number',
|
|
// min: 0
|
|
// },
|
|
fpv_steadycam_fov: {
|
|
name: t('dialog.config_json.fpv_steadycam_fov'),
|
|
default: '50',
|
|
type: 'number',
|
|
min: 30,
|
|
max: 110
|
|
}
|
|
})
|
|
);
|
|
|
|
const loading = ref(false);
|
|
|
|
watch(
|
|
() => isVRChatConfigDialogVisible.value,
|
|
async (newValue) => {
|
|
if (newValue) {
|
|
loading.value = true;
|
|
await readVRChatConfigFile();
|
|
loading.value = false;
|
|
}
|
|
}
|
|
);
|
|
|
|
const totalCacheSize = computed(() => {
|
|
return VRChatConfigFile.value.cache_size || VRChatTotalCacheSize.value;
|
|
});
|
|
|
|
const VRCHAT_RESOLUTION_DEFAULT_KEY = '__default__';
|
|
|
|
/**
|
|
*
|
|
* @param row
|
|
*/
|
|
function getVRChatResolutionKey(row) {
|
|
const width = Number(row?.width);
|
|
const height = Number(row?.height);
|
|
if (width > 0 && height > 0) {
|
|
return `${width}x${height}`;
|
|
}
|
|
return VRCHAT_RESOLUTION_DEFAULT_KEY;
|
|
}
|
|
|
|
const vrchatCameraResolutionKey = computed({
|
|
get: () => {
|
|
const width = Number(VRChatConfigFile.value.camera_res_width);
|
|
const height = Number(VRChatConfigFile.value.camera_res_height);
|
|
if (width > 0 && height > 0) {
|
|
return `${width}x${height}`;
|
|
}
|
|
return VRCHAT_RESOLUTION_DEFAULT_KEY;
|
|
},
|
|
set: (value) => {
|
|
const row = VRChatCameraResolutions.find((r) => getVRChatResolutionKey(r) === value);
|
|
if (row) {
|
|
setVRChatCameraResolution(row);
|
|
}
|
|
}
|
|
});
|
|
|
|
const vrchatSpoutResolutionKey = computed({
|
|
get: () => {
|
|
const width = Number(VRChatConfigFile.value.camera_spout_res_width);
|
|
const height = Number(VRChatConfigFile.value.camera_spout_res_height);
|
|
if (width > 0 && height > 0) {
|
|
return `${width}x${height}`;
|
|
}
|
|
return VRCHAT_RESOLUTION_DEFAULT_KEY;
|
|
},
|
|
set: (value) => {
|
|
const row = VRChatScreenshotResolutions.find((r) => getVRChatResolutionKey(r) === value);
|
|
if (row) {
|
|
setVRChatSpoutResolution(row);
|
|
}
|
|
}
|
|
});
|
|
|
|
const vrchatScreenshotResolutionKey = computed({
|
|
get: () => {
|
|
const width = Number(VRChatConfigFile.value.screenshot_res_width);
|
|
const height = Number(VRChatConfigFile.value.screenshot_res_height);
|
|
if (width > 0 && height > 0) {
|
|
return `${width}x${height}`;
|
|
}
|
|
return VRCHAT_RESOLUTION_DEFAULT_KEY;
|
|
},
|
|
set: (value) => {
|
|
const row = VRChatScreenshotResolutions.find((r) => getVRChatResolutionKey(r) === value);
|
|
if (row) {
|
|
setVRChatScreenshotResolution(row);
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function showDeleteAllVRChatCacheConfirm() {
|
|
modalStore
|
|
.confirm({
|
|
description: t('confirm.clear_cache'),
|
|
title: t('confirm.title')
|
|
})
|
|
.then(({ ok }) => {
|
|
if (ok) {
|
|
deleteAllVRChatCache();
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async function deleteAllVRChatCache() {
|
|
try {
|
|
await AssetBundleManager.DeleteAllCache();
|
|
toast.success(t('message.cache.deleted'));
|
|
} catch (error) {
|
|
toast.error(t('message.cache.delete_error', { error: error.message }));
|
|
}
|
|
getVRChatCacheSize();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param value
|
|
*/
|
|
async function openConfigFolderBrowser(value) {
|
|
const oldPath = VRChatConfigFile.value[value];
|
|
const newPath = await folderSelectorDialog(oldPath);
|
|
if (newPath) {
|
|
VRChatConfigFile.value[value] = newPath;
|
|
refreshDialogValues();
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function refreshDialogValues() {
|
|
loading.value = true;
|
|
loading.value = false;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param res
|
|
*/
|
|
function setVRChatSpoutResolution(res) {
|
|
VRChatConfigFile.value.camera_spout_res_height = res.height;
|
|
VRChatConfigFile.value.camera_spout_res_width = res.width;
|
|
refreshDialogValues();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param res
|
|
*/
|
|
function setVRChatCameraResolution(res) {
|
|
VRChatConfigFile.value.camera_res_height = res.height;
|
|
VRChatConfigFile.value.camera_res_width = res.width;
|
|
refreshDialogValues();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param res
|
|
*/
|
|
function setVRChatScreenshotResolution(res) {
|
|
VRChatConfigFile.value.screenshot_res_height = res.height;
|
|
VRChatConfigFile.value.screenshot_res_width = res.width;
|
|
refreshDialogValues();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function getVRChatCameraResolution() {
|
|
if (VRChatConfigFile.value.camera_res_height && VRChatConfigFile.value.camera_res_width) {
|
|
const res = `${VRChatConfigFile.value.camera_res_width}x${VRChatConfigFile.value.camera_res_height}`;
|
|
return getVRChatResolution(res);
|
|
}
|
|
return '1920x1080 (1080p)';
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function getVRChatSpoutResolution() {
|
|
if (VRChatConfigFile.value.camera_spout_res_height && VRChatConfigFile.value.camera_spout_res_width) {
|
|
const res = `${VRChatConfigFile.value.camera_spout_res_width}x${VRChatConfigFile.value.camera_spout_res_height}`;
|
|
return getVRChatResolution(res);
|
|
}
|
|
return '1920x1080 (1080p)';
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function getVRChatScreenshotResolution() {
|
|
if (VRChatConfigFile.value.screenshot_res_height && VRChatConfigFile.value.screenshot_res_width) {
|
|
const res = `${VRChatConfigFile.value.screenshot_res_width}x${VRChatConfigFile.value.screenshot_res_height}`;
|
|
return getVRChatResolution(res);
|
|
}
|
|
return '1920x1080 (1080p)';
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function saveVRChatConfigFile() {
|
|
for (const item in VRChatConfigFile.value) {
|
|
if (item === 'picture_output_split_by_date') {
|
|
// this one is default true, it's special
|
|
if (VRChatConfigFile.value[item]) {
|
|
delete VRChatConfigFile.value[item];
|
|
}
|
|
} else if (VRChatConfigFile.value[item] === '') {
|
|
delete VRChatConfigFile.value[item];
|
|
} else if (typeof VRChatConfigFile.value[item] === 'boolean' && VRChatConfigFile.value[item] === false) {
|
|
delete VRChatConfigFile.value[item];
|
|
} else if (typeof VRChatConfigFile.value[item] === 'string') {
|
|
const parsed = parseInt(VRChatConfigFile.value[item], 10);
|
|
if (!Number.isNaN(parsed)) {
|
|
VRChatConfigFile.value[item] = parsed;
|
|
}
|
|
}
|
|
}
|
|
WriteVRChatConfigFile();
|
|
closeDialog();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function WriteVRChatConfigFile() {
|
|
const json = JSON.stringify(VRChatConfigFile.value, null, '\t');
|
|
AppApi.WriteConfigFile(json);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async function readVRChatConfigFile() {
|
|
const config = await AppApi.ReadConfigFileSafe();
|
|
if (config) {
|
|
try {
|
|
const parsedConfig = JSON.parse(config);
|
|
VRChatConfigFile.value = { ...VRChatConfigFile.value, ...parsedConfig };
|
|
} catch {
|
|
toast.error(t('message.cache.invalid_config'));
|
|
throw new Error('Invalid JSON in config.json');
|
|
}
|
|
}
|
|
if (typeof VRChatConfigFile.value.picture_output_split_by_date === 'undefined') {
|
|
VRChatConfigFile.value.picture_output_split_by_date = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function closeDialog() {
|
|
isVRChatConfigDialogVisible.value = false;
|
|
}
|
|
</script>
|