forked from akanyan/STARTLINER
feat: new config format
This commit is contained in:
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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 {
|
||||
|
113
src/components/ProfileListEntry.vue
Normal file
113
src/components/ProfileListEntry.vue
Normal 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>
|
@ -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;
|
||||
|
Reference in New Issue
Block a user