feat: new config format

This commit is contained in:
2025-03-13 23:26:00 +00:00
parent 48dc9ec4df
commit fd27000c05
30 changed files with 1447 additions and 833 deletions

View File

@ -30,8 +30,7 @@ onMounted(async () => {
general.dirs = d as Dirs;
});
await prf.reloadList();
await prf.reload();
await Promise.all([prf.reloadList(), prf.reload()]);
if (prf.current !== null) {
await pkg.reloadAll();

View File

@ -2,25 +2,16 @@
import Button from 'primevue/button';
import InputText from 'primevue/inputtext';
import { open } from '@tauri-apps/plugin-dialog';
import { usePrfStore } from '../stores';
const props = defineProps({
field: String,
default: String,
placeholder: String,
directory: Boolean,
promptname: String,
extension: String,
value: String,
callback: Function,
});
if (props.field === undefined || props.default === undefined) {
throw new Error('Invalid FilePicker');
}
const prf = usePrfStore();
const cfg = prf.cfg(props.field, props.default);
const filePick = async () => {
const res = await open({
multiple: false,
@ -35,9 +26,9 @@ const filePick = async () => {
]
: [],
});
if (res != null) {
cfg.value =
/*path.relative(cfgs.current?.data.exe_dir ?? '', res) */ res;
if (res != null && props.callback !== undefined) {
props.callback(res);
/*path.relative(cfgs.current?.data.exe_dir ?? '', res) */
}
};
</script>
@ -48,6 +39,7 @@ const filePick = async () => {
size="small"
:placeholder="placeholder"
type="text"
v-model="cfg"
:model-value="value"
@update:model-value="(value) => callback && callback(value)"
/>
</template>

View File

@ -66,7 +66,7 @@ const aimeCodeModel = computed({
});
const extraDisplayOptionsDisabled = computed(() => {
return prf.cfg('display', 'default').value === 'default';
return prf.current?.display.target === 'default';
});
(async () => {
@ -79,16 +79,16 @@ const extraDisplayOptionsDisabled = computed(() => {
<OptionCategory title="General">
<OptionRow title="mu3.exe">
<FilePicker
field="target-path"
default=""
:directory="false"
promptname="mu3.exe"
extension="exe"
:value="prf.current!.sgt.target"
:callback="(value: string) => (prf.current!.sgt.target = value)"
></FilePicker>
</OptionRow>
<OptionRow title="mu3hook">
<Select
:model-value="prf.cfg('hook', 'segatools-mu3hook')"
model-value="segatools-mu3hook"
:options="hookList"
option-label="title"
option-value="value"
@ -96,24 +96,27 @@ const extraDisplayOptionsDisabled = computed(() => {
</OptionRow>
<OptionRow title="amfs">
<FilePicker
field="amfs"
default=""
placeholder="amfs"
:directory="true"
placeholder="amfs"
:value="prf.current!.sgt.amfs"
:callback="(value: string) => (prf.current!.sgt.amfs = value)"
></FilePicker>
</OptionRow>
<OptionRow title="option">
<FilePicker
field="option"
default="option"
:directory="true"
placeholder="option"
:value="prf.current!.sgt.option"
:callback="(value: string) => (prf.current!.sgt.option = value)"
></FilePicker>
</OptionRow>
<OptionRow title="appdata">
<FilePicker
field="appdata"
default="appdata"
:directory="true"
:value="prf.current!.sgt.appdata"
:callback="
(value: string) => (prf.current!.sgt.appdata = value)
"
></FilePicker>
</OptionRow>
</OptionCategory>
@ -123,7 +126,7 @@ const extraDisplayOptionsDisabled = computed(() => {
title="Target display"
>
<Select
:model-value="prf.cfg('display', 'default')"
v-model="prf.current!.display.target"
:options="displayList"
option-label="title"
option-value="value"
@ -136,7 +139,7 @@ const extraDisplayOptionsDisabled = computed(() => {
:min="480"
:max="9999"
:use-grouping="false"
:model-value="prf.cfgAny('rez-w', 1080)"
v-model="prf.current!.display.rez[0]"
/>
x
<InputNumber
@ -145,17 +148,18 @@ const extraDisplayOptionsDisabled = computed(() => {
:min="640"
:max="9999"
:use-grouping="false"
:model-value="prf.cfgAny('rez-h', 1920)"
v-model="prf.current!.display.rez[1]"
/>
</OptionRow>
<OptionRow title="Display mode">
<SelectButton
:model-value="prf.cfg('display-mode', 'borderless')"
v-model="prf.current!.display.mode"
:options="[
{ title: 'Window', value: 'window' },
{ title: 'Borderless window', value: 'borderless' },
{ title: 'Fullscreen', value: 'fullscreen' },
{ title: 'Window', value: 'Window' },
{ title: 'Borderless window', value: 'Borderless' },
{ title: 'Fullscreen', value: 'Fullscreen' },
]"
:allow-empty="false"
option-label="title"
option-value="value"
/>
@ -165,12 +169,13 @@ const extraDisplayOptionsDisabled = computed(() => {
v-if="capabilities.includes('display')"
>
<SelectButton
:model-value="prf.cfg('display-rotation', 0)"
v-model="prf.current!.display.rotation"
:options="[
{ title: 'Unchanged', value: 0 },
{ title: 'Portrait', value: 90 },
{ title: 'Portrait (flipped)', value: 270 },
]"
:allow-empty="false"
option-label="title"
option-value="value"
:disabled="extraDisplayOptionsDisabled"
@ -183,7 +188,7 @@ const extraDisplayOptionsDisabled = computed(() => {
:min="60"
:max="999"
:use-grouping="false"
:model-value="prf.cfgAny('frequency', 60)"
v-model="prf.current!.display.frequency"
:disabled="extraDisplayOptionsDisabled"
/>
</OptionRow>
@ -191,32 +196,69 @@ const extraDisplayOptionsDisabled = computed(() => {
title="Match display resolution with the game"
v-if="capabilities.includes('display')"
>
<!-- @vue-expect-error -->
<ToggleSwitch
:disabled="
extraDisplayOptionsDisabled ||
prf.cfg('display-mode', 'borderless').value != 'borderless'
prf.current?.display.mode !== 'Borderless'
"
:model-value="prf.cfg('borderless-fullscreen', false)"
v-model="prf.current!.display.borderless_fullscreen"
/>
</OptionRow>
</OptionCategory>
<OptionCategory title="Network">
<OptionRow title="Server address">
<OptionRow title="Network type">
<SelectButton
v-model="prf.current!.network.network_type"
:options="[
{ title: 'Remote', value: 'Remote' },
{ title: 'Local (ARTEMiS)', value: 'Artemis' },
]"
:allow-empty="false"
option-label="title"
option-value="value"
/>
</OptionRow>
<OptionRow
v-if="prf.current!.network.network_type == 'Artemis'"
title="ARTEMiS path"
>
<FilePicker
:directory="false"
promptname="index.py"
extension="py"
:value="prf.current!.network.local_path"
:callback="
(value: string) => (prf.current!.network.local_path = value)
"
></FilePicker>
</OptionRow>
<OptionRow
v-if="prf.current!.network.network_type == 'Artemis'"
title="ARTEMiS console"
>
<ToggleSwitch v-model="prf.current!.network.local_console" />
</OptionRow>
<OptionRow
v-if="prf.current!.network.network_type == 'Remote'"
title="Server address"
>
<InputText
class="shrink"
size="small"
:maxlength="40"
placeholder="192.168.1.234"
:model-value="prf.cfgAny<string>('dns-default', '')"
v-model="prf.current!.network.remote_address"
/> </OptionRow
><OptionRow title="Keychip">
><OptionRow
v-if="prf.current!.network.network_type == 'Remote'"
title="Keychip"
>
<InputText
class="shrink"
size="small"
:maxlength="16"
placeholder="A123-01234567890"
:model-value="prf.cfgAny('keychip', '')"
v-model="prf.current!.network.keychip"
/> </OptionRow
><OptionRow title="Subnet">
<InputText
@ -224,33 +266,33 @@ const extraDisplayOptionsDisabled = computed(() => {
size="small"
:maxlength="15"
placeholder="192.168.1.0"
:model-value="prf.cfgAny('subnet', '')"
v-model="prf.current!.network.subnet"
/>
</OptionRow>
<OptionRow title="Address suffix">
<InputText
<InputNumber
class="shrink"
size="small"
:maxlength="3"
:min="0"
:max="255"
placeholder="12"
:model-value="prf.cfgAny('addrsuffix', '')"
v-model="prf.current!.network.suffix"
/>
</OptionRow>
</OptionCategory>
<OptionCategory title="Misc">
<OptionRow title="OpenSSL bug workaround for Intel ≥10th gen">
<!-- @vue-expect-error -->
<ToggleSwitch :model-value="prf.cfg('intel', false)" />
<ToggleSwitch v-model="prf.current!.sgt.intel" />
</OptionRow>
<OptionRow title="Aime emulation">
<!-- @vue-expect-error -->
<ToggleSwitch :model-value="prf.cfg('aime', false)" />
<ToggleSwitch v-model="prf.current!.sgt.enable_aime" />
</OptionRow>
<OptionRow title="Aime code">
<InputText
class="shrink"
size="small"
:disabled="prf.cfg<boolean>('aime', false).value !== true"
:disabled="prf.current?.sgt.enable_aime !== true"
:maxlength="20"
placeholder="00000000000000000000"
v-model="aimeCodeModel"
@ -268,6 +310,9 @@ const extraDisplayOptionsDisabled = computed(() => {
extension="cfg"
/>
</OptionRow>
<OptionRow title="BepInEx console">
<ToggleSwitch v-model="prf.current!.bepinex.console" />
</OptionRow>
</OptionCategory>
</template>

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import Button from 'primevue/button';
import ProfileListEntry from './ProfileListEntry.vue';
import { usePrfStore } from '../stores';
const prf = usePrfStore();
@ -18,70 +19,25 @@ const prf = usePrfStore();
icon="pi pi-plus"
class="chunithm-button profile-button"
@click="() => prf.create('chunithm')"
:disabled="true"
/>
</div>
<div class="mt-12 flex flex-col flex-wrap align-middle gap-4">
<div v-for="p in prf.list">
<div class="flex flex-row flex-wrap align-middle gap-2">
<Button
:disabled="
prf.current?.game === p.game &&
prf.current?.name === p.name
"
:label="p.name"
:class="
(p.game === 'chunithm'
? 'chunithm-button'
: 'ongeki-button') +
' ' +
'self-center profile-button'
"
@click="prf.switchTo(p.game, p.name)"
/>
<Button
rounded
icon="pi pi-trash"
severity="danger"
aria-label="remove"
size="small"
class="self-center ml-2"
style="width: 2rem; height: 2rem"
:disabled="true"
/>
<Button
rounded
icon="pi pi-clone"
severity="warn"
aria-label="duplicate"
size="small"
class="self-center"
style="width: 2rem; height: 2rem"
:disabled="true"
/>
<Button
rounded
icon="pi pi-pencil"
severity="help"
aria-label="duplicate"
size="small"
class="self-center"
style="width: 2rem; height: 2rem"
:disabled="true"
/>
</div>
<ProfileListEntry :p="p" />
</div>
</div>
</template>
<style scoped>
<style>
.profile-button {
width: 14em;
white-space: nowrap;
}
.ongeki-button {
background-color: var(--p-pink-400);
border-color: var(--p-pink-400);
background-color: var(--p-pink-400) !important;
border-color: var(--p-pink-400) !important;
}
.ongeki-button:hover,
@ -91,8 +47,8 @@ const prf = usePrfStore();
}
.chunithm-button {
background-color: var(--p-yellow-400);
border-color: var(--p-yellow-400);
background-color: var(--p-yellow-400) !important;
border-color: var(--p-yellow-400) !important;
}
.chunithm-button:hover,
.chunithm-button:active {

View File

@ -0,0 +1,113 @@
<script setup lang="ts">
import { ref } from 'vue';
import Button from 'primevue/button';
import InputText from 'primevue/inputtext';
import * as path from '@tauri-apps/api/path';
import { open } from '@tauri-apps/plugin-shell';
import { useGeneralStore, usePrfStore } from '../stores';
import { ProfileMeta } from '../types';
const prf = usePrfStore();
const general = useGeneralStore();
const isEditing = ref(false);
const props = defineProps({
p: Object as () => ProfileMeta,
});
if (props.p === undefined) {
throw new Error('Invalid ProfileListEntry');
}
const rename = async (event: KeyboardEvent) => {
if (event.key !== 'Enter') {
return;
}
isEditing.value = false;
if (
event.target !== null &&
'value' in event.target &&
typeof event.target.value === 'string'
) {
const value = event.target.value
.replaceAll('..', '')
.replaceAll('\\', '')
.replaceAll('/', '');
if (value.length > 0) {
await prf.rename(props.p!, value);
}
}
};
</script>
<template>
<div class="flex flex-row flex-wrap align-middle gap-2">
<Button
:disabled="
prf.current?.game === p!.game && prf.current?.name === p!.name
"
:class="
(p!.game === 'chunithm' ? 'chunithm-button' : 'ongeki-button') +
' ' +
'self-center profile-button'
"
@click="prf.switchTo(p!.game, p!.name)"
>
<div v-if="!isEditing">{{ p!.name }}</div>
<div v-else>
<InputText
:model-value="p!.name"
@vue:mounted="$event?.el?.focus()"
@keyup="rename"
@focusout="isEditing = false"
>
</InputText></div
></Button>
<Button
rounded
icon="pi pi-trash"
severity="danger"
aria-label="remove"
size="small"
class="self-center ml-2"
style="width: 2rem; height: 2rem"
:disabled="true"
/>
<Button
rounded
icon="pi pi-clone"
severity="help"
aria-label="duplicate"
size="small"
class="self-center"
style="width: 2rem; height: 2rem"
:disabled="true"
/>
<Button
rounded
icon="pi pi-pencil"
severity="help"
aria-label="rename"
size="small"
class="self-center"
style="width: 2rem; height: 2rem"
@click="isEditing = true"
/>
<Button
rounded
icon="pi pi-folder"
severity="help"
aria-label="open-directory"
size="small"
class="self-center"
style="width: 2rem; height: 2rem"
@click="
path
.join(general.dataDir, `profile-${p!.game}-${p!.name}`)
.then(open)
"
/>
</div>
</template>

View File

@ -25,10 +25,10 @@ const kill = async () => {
};
const disabledTooltip = computed(() => {
if (prf.cfg('target-path', '').value.length === 0) {
if (prf.current?.sgt.target.length === 0) {
return 'The game path must be specified';
}
if (prf.cfg('amfs', '').value.length === 0) {
if (prf.current?.sgt.amfs.length === 0) {
return 'The amfs path must be specified';
}
return null;