feat: more breaking changes

This commit is contained in:
2025-03-06 20:38:18 +00:00
parent 39ba6a5891
commit cda8230d7d
19 changed files with 638 additions and 559 deletions

View File

@ -11,10 +11,13 @@ import ModStore from './ModStore.vue';
import OptionList from './OptionList.vue';
import ProfileList from './ProfileList.vue';
import StartButton from './StartButton.vue';
import { usePkgStore, usePrfStore } from '../stores';
import { invoke } from '../invoke';
import { useGeneralStore, usePkgStore, usePrfStore } from '../stores';
import { Dirs } from '../types';
const pkg = usePkgStore();
const prf = usePrfStore();
const general = useGeneralStore();
pkg.setupListeners();
@ -23,6 +26,10 @@ const currentTab = ref('3');
const isProfileDisabled = computed(() => prf.current === null);
onMounted(async () => {
invoke('list_directories').then((d) => {
general.dirs = d as Dirs;
});
await prf.reloadList();
await prf.reload();
@ -65,27 +72,10 @@ onMounted(async () => {
<OptionList />
</TabPanel>
<TabPanel value="3">
<strong>UNDER CONSTRUCTION</strong><br />Many features are
<strong>UNDER CONSTRUCTION</strong><br />Some features are
missing.<br />Existing features are expected to break any
time.
<div v-if="isProfileDisabled">
<br />Select <code>mu3.exe</code> to create a profile:
</div>
<ProfileList />
<div v-if="isProfileDisabled">
<div
style="
margin-top: 5px;
font-weight: bolder;
font-size: 3em;
color: red;
line-height: 1.1em;
"
>
segatools 2024-12-23 or later has to be installed
</div>
(this will change in the future)
</div>
<img
v-if="prf.current?.game === 'ongeki'"
src="/sticker-ongeki.svg"
@ -97,13 +87,23 @@ onMounted(async () => {
class="fixed bottom-0 right-0"
/>
<br /><br /><br />
<Button
style="position: fixed; left: 10px; bottom: 10px"
icon="pi pi-discord"
as="a"
target="_blank"
href="https://discord.gg/jxvzHjjEmc"
/>
<footer
style="
position: fixed;
left: 0px;
bottom: 0px;
height: 40px;
width: 100%;
"
>
<Button
style="margin-left: 6px"
icon="pi pi-discord"
as="a"
target="_blank"
href="https://discord.gg/jxvzHjjEmc"
/>
</footer>
</TabPanel>
</TabPanels>
</Tabs>

View File

@ -4,7 +4,9 @@ import Button from 'primevue/button';
import * as path from '@tauri-apps/api/path';
import { open } from '@tauri-apps/plugin-dialog';
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
import { invoke } from '../invoke';
import { usePrfStore } from '../stores';
const prf = usePrfStore();
const props = defineProps({
filename: String,
@ -53,13 +55,10 @@ const filePick = async () => {
};
(async () => {
const profileDir: string = await invoke('get_current_profile_dir');
if (props.filename === undefined) {
throw new Error('FileEditor without a filename');
}
target_path.value = await path.join(profileDir, props.filename);
target_path.value = await path.join(await prf.configDir, props.filename);
await load(target_path.value);
})();
</script>

View File

@ -5,7 +5,8 @@ import InputText from 'primevue/inputtext';
import Select from 'primevue/select';
import SelectButton from 'primevue/selectbutton';
import ToggleSwitch from 'primevue/toggleswitch';
import { invoke as unproxied_invoke } from '@tauri-apps/api/core';
import * as path from '@tauri-apps/api/path';
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
import FileEditor from './FileEditor.vue';
import FilePicker from './FilePicker.vue';
import OptionCategory from './OptionCategory.vue';
@ -32,20 +33,6 @@ const hookList: Ref<{ title: string; value: string }[]> = ref([
},
]);
unproxied_invoke('read_profile_data', {
path: 'aime.txt',
})
.then((v: unknown) => {
if (typeof v === 'string') {
aimeCode.value = v;
} else {
aimeCode.value = '';
}
})
.catch(() => {
aimeCode.value = '';
});
invoke('list_platform_capabilities')
.then(async (v: unknown) => {
if (Array.isArray(v)) {
@ -72,26 +59,33 @@ const aimeCodeModel = computed({
async set(value: string) {
aimeCode.value = value;
if (value.match(/^[0-9]{20}$/)) {
await invoke('write_profile_data', {
path: 'aime.txt',
content: aimeCode.value,
});
const aime_path = await path.join(await prf.configDir, 'aime.txt');
await writeTextFile(aime_path, aimeCode.value);
}
},
});
const extraDisplayOptionsDisabled = computed(() => {
return prf.cfg('display', 'default').value === 'default';
});
(async () => {
const aime_path = await path.join(await prf.configDir, 'aime.txt');
aimeCode.value = await readTextFile(aime_path);
})();
</script>
<template>
<OptionCategory title="General">
<!-- <OptionRow title="mu3.exe">
<OptionRow title="mu3.exe">
<FilePicker
field="game-dir"
field="target-path"
default=""
:directory="false"
promptname="mu3.exe"
extension="exe"
></FilePicker>
</OptionRow> -->
</OptionRow>
<OptionRow title="mu3hook">
<Select
:model-value="prf.cfg('hook', 'segatools-mu3hook')"
@ -135,17 +129,14 @@ const aimeCodeModel = computed({
option-value="value"
></Select>
</OptionRow>
<OptionRow id="resolution" title="Game resolution">
<OptionRow class="number-input" title="Game resolution">
<InputNumber
class="shrink"
size="small"
:min="480"
:max="9999"
:use-grouping="false"
:model-value="
// prettier-ignore Because primevue fucked up
prf.cfg('rez-w', 1080) as any
"
:model-value="prf.cfgAny('rez-w', 1080)"
/>
x
<InputNumber
@ -154,10 +145,7 @@ const aimeCodeModel = computed({
:min="640"
:max="9999"
:use-grouping="false"
:model-value="
// prettier-ignore
prf.cfg('rez-h', 1920) as any
"
:model-value="prf.cfgAny('rez-h', 1920)"
/>
</OptionRow>
<OptionRow title="Display mode">
@ -185,7 +173,18 @@ const aimeCodeModel = computed({
]"
option-label="title"
option-value="value"
:disabled="prf.cfg('display', 'default').value === 'default'"
:disabled="extraDisplayOptionsDisabled"
/>
</OptionRow>
<OptionRow class="number-input" title="Refresh Rate">
<InputNumber
class="shrink"
size="small"
:min="60"
:max="999"
:use-grouping="false"
:model-value="prf.cfgAny('frequency', 60)"
:disabled="extraDisplayOptionsDisabled"
/>
</OptionRow>
<OptionRow
@ -195,7 +194,7 @@ const aimeCodeModel = computed({
<!-- @vue-expect-error -->
<ToggleSwitch
:disabled="
prf.cfg('display', 'default').value === 'default' ||
extraDisplayOptionsDisabled ||
prf.cfg('display-mode', 'borderless').value != 'borderless'
"
:model-value="prf.cfg('borderless-fullscreen', false)"
@ -209,10 +208,7 @@ const aimeCodeModel = computed({
size="small"
:maxlength="40"
placeholder="192.168.1.234"
:model-value="
// prettier-ignore
prf.cfg<string>('dns-default', '') as any
"
:model-value="prf.cfgAny<string>('dns-default', '')"
/> </OptionRow
><OptionRow title="Keychip">
<InputText
@ -220,10 +216,7 @@ const aimeCodeModel = computed({
size="small"
:maxlength="16"
placeholder="A123-01234567890"
:model-value="
// prettier-ignore
prf.cfg('keychip', '') as any
"
:model-value="prf.cfgAny('keychip', '')"
/> </OptionRow
><OptionRow title="Subnet">
<InputText
@ -231,10 +224,7 @@ const aimeCodeModel = computed({
size="small"
:maxlength="15"
placeholder="192.168.1.0"
:model-value="
// prettier-ignore
prf.cfg('subnet', '') as any
"
:model-value="prf.cfgAny('subnet', '')"
/>
</OptionRow>
</OptionCategory>
@ -271,7 +261,7 @@ const aimeCodeModel = computed({
</template>
<style>
#resolution .p-inputnumber-input {
.number-input .p-inputnumber-input {
width: 4rem;
}

View File

@ -9,11 +9,17 @@ const prf = usePrfStore();
<div class="mt-4 flex flex-wrap align-middle gap-4">
<Button
:disabled="prf.list.length > 0"
label="Create profile"
label="O.N.G.E.K.I. profile"
icon="pi pi-plus"
aria-label="open-executable"
class="create-button"
@click="prf.prompt"
class="ongeki-button"
@click="() => prf.create('ongeki')"
/>
<Button
:disabled="prf.list.length > 0"
label="CHUNITHM profile"
icon="pi pi-plus"
class="chunithm-button"
@click="() => prf.create('chunithm')"
/>
<div v-for="p in prf.list">
@ -51,7 +57,7 @@ const prf = usePrfStore();
.ongeki-button {
background-color: var(--p-pink-400);
border-color: var(--p-pink-400);
width: 10em;
width: 14em;
}
.ongeki-button:hover,
@ -63,7 +69,7 @@ const prf = usePrfStore();
.chunithm-button {
background-color: var(--p-yellow-400);
border-color: var(--p-yellow-400);
width: 10em;
width: 14em;
}
.chunithm-button:hover,
.chunithm-button:active {

View File

@ -1,8 +1,11 @@
<script setup lang="ts">
import { Ref, ref } from 'vue';
import { Ref, computed, ref } from 'vue';
import Button from 'primevue/button';
import { listen } from '@tauri-apps/api/event';
import { invoke } from '../invoke';
import { usePrfStore } from '../stores';
const prf = usePrfStore();
type StartStatus = 'ready' | 'preparing' | 'running';
const startStatus: Ref<StartStatus> = ref('ready');
@ -21,6 +24,16 @@ const kill = async () => {
startStatus.value = 'ready';
};
const disabledTooltip = computed(() => {
if (prf.cfg('target-path', '').value.length === 0) {
return 'The game path must be specified';
}
if (prf.cfg('amfs', '').value.length === 0) {
return 'The amfs path must be specified';
}
return null;
});
listen('launch-start', () => {
startStatus.value = 'running';
});
@ -33,7 +46,8 @@ listen('launch-end', () => {
<template>
<Button
v-if="startStatus === 'ready'"
:disabled="false"
v-tooltip="disabledTooltip"
:disabled="disabledTooltip !== null"
icon="pi pi-play"
label="START"
aria-label="start"

View File

@ -3,6 +3,7 @@ import { createPinia } from 'pinia';
import { definePreset } from '@primevue/themes';
import Theme from '@primevue/themes/aura';
import PrimeVue from 'primevue/config';
import Tooltip from 'primevue/tooltip';
import App from './components/App.vue';
const pinia = createPinia();
@ -16,4 +17,5 @@ app.use(PrimeVue, {
preset: Preset,
},
});
app.directive('tooltip', Tooltip);
app.mount('#app');

View File

@ -1,15 +1,40 @@
import { Ref, computed, ref } from 'vue';
import { defineStore } from 'pinia';
import { listen } from '@tauri-apps/api/event';
import { open } from '@tauri-apps/plugin-dialog';
import * as path from '@tauri-apps/api/path';
import { invoke } from './invoke';
import { Game, Package, Profile, ProfileMeta } from './types';
import { Dirs, Game, Package, Profile, ProfileMeta } from './types';
import { changePrimaryColor, pkgKey } from './util';
type InstallStatus = {
pkg: string;
};
export const useGeneralStore = defineStore('general', () => {
const dirs: Ref<Dirs | null> = ref(null);
const configDir = computed(() => {
if (dirs.value === null) {
throw new Error('Invalid directory access');
}
return dirs.value.config_dir;
});
const dataDir = computed(() => {
if (dirs.value === null) {
throw new Error('Invalid directory access');
}
return dirs.value.data_dir;
});
const cacheDir = computed(() => {
if (dirs.value === null) {
throw new Error('Invalid directory access');
}
return dirs.value.cache_dir;
});
return { dirs, configDir, dataDir, cacheDir };
});
export const usePkgStore = defineStore('pkg', {
state: (): { pkg: { [key: string]: Package } } => {
return {
@ -113,25 +138,15 @@ export const usePrfStore = defineStore('prf', () => {
},
});
const prompt = async () => {
const exePath = await open({
multiple: false,
directory: false,
filters: [
{
name: 'mu3.exe or chusanApp.exe',
extensions: ['exe'],
},
],
});
if (exePath !== null) {
await create(exePath);
}
};
// Hack around PrimeVu not supporting WritableComputedRef
const cfgAny = <T extends string | boolean | number>(
key: string,
dflt: T
) => cfg(key, dflt) as any;
const create = async (exePath: string) => {
const create = async (game: Game) => {
try {
await invoke('init_profile', { exePath });
await invoke('init_profile', { game, name: 'new-profile' });
await reload();
await reloadList();
} catch (e) {
@ -173,6 +188,15 @@ export const usePrfStore = defineStore('prf', () => {
await save();
};
const generalStore = useGeneralStore();
const configDir = computed(async () => {
return await path.join(
generalStore.configDir,
`profile-${current.value?.game}-${current.value?.name}`
);
});
listen<InstallStatus>('install-end', async () => {
await reload();
});
@ -184,10 +208,11 @@ export const usePrfStore = defineStore('prf', () => {
reload,
save,
cfg,
prompt,
cfgAny,
create,
switchTo,
reloadList,
togglePkg,
configDir,
};
});

View File

@ -28,8 +28,13 @@ export interface ProfileMeta {
export interface Profile extends ProfileMeta {
data: {
exe_dir: string;
mods: string[];
cfg: { [key: string]: string | boolean | number };
};
}
export interface Dirs {
config_dir: string;
data_dir: string;
cache_dir: string;
}