forked from akanyan/STARTLINER
feat: more breaking changes
This commit is contained in:
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
|
@ -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');
|
||||
|
@ -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,
|
||||
};
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user