forked from akanyan/STARTLINER
feat: initial support for segatools pkgs
This commit is contained in:
@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { Ref, computed, onMounted, ref } from 'vue';
|
||||
import Button from 'primevue/button';
|
||||
import InputIcon from 'primevue/inputicon';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import Tab from 'primevue/tab';
|
||||
import TabList from 'primevue/tablist';
|
||||
import TabPanel from 'primevue/tabpanel';
|
||||
@ -21,7 +23,9 @@ const general = useGeneralStore();
|
||||
|
||||
pkg.setupListeners();
|
||||
|
||||
const currentTab = ref('3');
|
||||
const currentTab: Ref<string | number> = ref(3);
|
||||
const searchPkg = ref('');
|
||||
const searchCfg = ref('');
|
||||
|
||||
const isProfileDisabled = computed(() => prf.current === null);
|
||||
|
||||
@ -30,47 +34,81 @@ onMounted(async () => {
|
||||
general.dirs = d as Dirs;
|
||||
});
|
||||
|
||||
const fetch_promise = pkg.fetch();
|
||||
|
||||
await Promise.all([prf.reloadList(), prf.reload()]);
|
||||
|
||||
if (prf.current !== null) {
|
||||
await pkg.reloadAll();
|
||||
currentTab.value = '0';
|
||||
currentTab.value = 0;
|
||||
}
|
||||
|
||||
fetch_promise.then(async () => {
|
||||
await invoke('install_package', {
|
||||
key: 'segatools-mu3hook',
|
||||
force: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<Tabs lazy :value="currentTab" class="h-screen">
|
||||
<Tabs
|
||||
lazy
|
||||
:value="currentTab"
|
||||
v-on:update:value="(value) => (currentTab = value)"
|
||||
class="h-screen"
|
||||
>
|
||||
<div class="fixed w-full flex z-100">
|
||||
<TabList class="grow">
|
||||
<Tab :disabled="isProfileDisabled" value="0"
|
||||
<Tab :disabled="isProfileDisabled" :value="0"
|
||||
><div class="pi pi-list-check"></div
|
||||
></Tab>
|
||||
<Tab :disabled="isProfileDisabled" value="1"
|
||||
<Tab :disabled="isProfileDisabled" :value="1"
|
||||
><div class="pi pi-download"></div
|
||||
></Tab>
|
||||
<Tab :disabled="isProfileDisabled" value="2"
|
||||
<Tab :disabled="isProfileDisabled" :value="2"
|
||||
><div class="pi pi-cog"></div
|
||||
></Tab>
|
||||
<Tab value="3"
|
||||
<Tab :value="3"
|
||||
><div class="pi pi-question-circle"></div
|
||||
></Tab>
|
||||
<div class="grow"></div>
|
||||
<div class="flex" v-if="currentTab !== 3">
|
||||
<InputIcon class="self-center mr-2">
|
||||
<i class="pi pi-search" />
|
||||
</InputIcon>
|
||||
<InputText
|
||||
v-if="currentTab === 2"
|
||||
class="self-center"
|
||||
size="small"
|
||||
placeholder="Search"
|
||||
v-model="searchCfg"
|
||||
/>
|
||||
<InputText
|
||||
v-else
|
||||
class="self-center"
|
||||
size="small"
|
||||
placeholder="Search"
|
||||
v-model="searchPkg"
|
||||
/>
|
||||
</div>
|
||||
<div class="grow"></div>
|
||||
<StartButton />
|
||||
</TabList>
|
||||
</div>
|
||||
<TabPanels class="w-full grow mt-[3rem]">
|
||||
<TabPanel value="0">
|
||||
<ModList />
|
||||
<TabPanel :value="0">
|
||||
<ModList :search="searchPkg" />
|
||||
</TabPanel>
|
||||
<TabPanel value="1">
|
||||
<ModStore />
|
||||
<TabPanel :value="1">
|
||||
<ModStore :search="searchPkg" />
|
||||
</TabPanel>
|
||||
<TabPanel value="2">
|
||||
<TabPanel :value="2">
|
||||
<OptionList />
|
||||
</TabPanel>
|
||||
<TabPanel value="3">
|
||||
<TabPanel :value="3">
|
||||
<strong>UNDER CONSTRUCTION</strong><br />Some features are
|
||||
missing.<br />Existing features are expected to break
|
||||
sometimes.
|
||||
|
@ -14,7 +14,10 @@ const install = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
await invoke('install_package', { key: pkgKey(props.pkg) });
|
||||
await invoke('install_package', {
|
||||
key: pkgKey(props.pkg),
|
||||
force: true,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (props.pkg !== undefined) {
|
||||
|
@ -1,29 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import Fieldset from 'primevue/fieldset';
|
||||
import ModListEntry from './ModListEntry.vue';
|
||||
import { usePkgStore, usePrfStore } from '../stores';
|
||||
import { usePkgStore } from '../stores';
|
||||
|
||||
const props = defineProps({
|
||||
search: String,
|
||||
});
|
||||
|
||||
const pkg = usePkgStore();
|
||||
const prf = usePrfStore();
|
||||
const empty = ref(true);
|
||||
|
||||
const group = () => {
|
||||
const a = Object.assign(
|
||||
{},
|
||||
Object.groupBy(
|
||||
pkg.allLocal
|
||||
.filter(
|
||||
(p) =>
|
||||
props.search === undefined ||
|
||||
p.name
|
||||
.toLowerCase()
|
||||
.includes(props.search.toLowerCase()) ||
|
||||
p.namespace
|
||||
.toLowerCase()
|
||||
.includes(props.search.toLowerCase())
|
||||
)
|
||||
.sort((p1, p2) => p1.namespace.localeCompare(p2.namespace))
|
||||
.sort((p1, p2) => p1.name.localeCompare(p2.name)),
|
||||
({ namespace }) => namespace
|
||||
)
|
||||
);
|
||||
empty.value = Object.keys(a).length === 0;
|
||||
return a;
|
||||
};
|
||||
|
||||
prf.reload();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Fieldset v-for="(namespace, key) in group()" :legend="key.toString()">
|
||||
<ModListEntry v-for="p in namespace" :pkg="p" />
|
||||
</Fieldset>
|
||||
<div v-if="empty" class="text-3xl">∅</div>
|
||||
</template>
|
||||
|
@ -1,24 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import ModStoreEntry from './ModStoreEntry.vue';
|
||||
import { usePkgStore } from '../stores';
|
||||
|
||||
const pkgs = usePkgStore();
|
||||
const empty = ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
pkgs.fetch();
|
||||
const props = defineProps({
|
||||
search: String,
|
||||
});
|
||||
|
||||
const list = () => {
|
||||
const res = pkgs.allRemote
|
||||
.filter(
|
||||
(p) =>
|
||||
props.search === undefined ||
|
||||
p.name.toLowerCase().includes(props.search.toLowerCase()) ||
|
||||
p.namespace
|
||||
.toLowerCase()
|
||||
.includes(props.search.toLowerCase()) ||
|
||||
p.description.toLowerCase().includes(props.search.toLowerCase())
|
||||
)
|
||||
.sort((p1, p2) => p1.name.localeCompare(p2.name));
|
||||
empty.value = res.length === 0;
|
||||
return res;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-for="p in pkgs.allRemote.sort((p1, p2) =>
|
||||
p1.name.localeCompare(p2.name)
|
||||
)"
|
||||
class="flex flex-row"
|
||||
>
|
||||
<div v-for="p in list()" class="flex flex-row">
|
||||
<ModStoreEntry :pkg="p" />
|
||||
</div>
|
||||
<div v-if="empty" class="text-3xl">∅</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -12,8 +12,10 @@ import FilePicker from './FilePicker.vue';
|
||||
import OptionCategory from './OptionCategory.vue';
|
||||
import OptionRow from './OptionRow.vue';
|
||||
import { invoke } from '../invoke';
|
||||
import { usePrfStore } from '../stores';
|
||||
import { usePkgStore, usePrfStore } from '../stores';
|
||||
import { pkgKey } from '../util';
|
||||
|
||||
const pkg = usePkgStore();
|
||||
const prf = usePrfStore();
|
||||
|
||||
const aimeCode = ref('');
|
||||
@ -26,13 +28,6 @@ const displayList: Ref<{ title: string; value: string }[]> = ref([
|
||||
},
|
||||
]);
|
||||
|
||||
// const hookList: Ref<{ title: string; value: string }[]> = ref([
|
||||
// {
|
||||
// title: 'segatools-mu3hook',
|
||||
// value: 'segatools-mu3hook',
|
||||
// },
|
||||
// ]);
|
||||
|
||||
invoke('list_platform_capabilities')
|
||||
.then(async (v: unknown) => {
|
||||
if (Array.isArray(v)) {
|
||||
@ -86,14 +81,18 @@ const extraDisplayOptionsDisabled = computed(() => {
|
||||
:callback="(value: string) => (prf.current!.sgt.target = value)"
|
||||
></FilePicker>
|
||||
</OptionRow>
|
||||
<!-- <OptionRow title="mu3hook">
|
||||
<OptionRow title="mu3hook">
|
||||
<Select
|
||||
model-value="segatools-mu3hook"
|
||||
:options="hookList"
|
||||
v-model="prf.current!.sgt.hook"
|
||||
:options="
|
||||
pkg.hooks.map((p) => {
|
||||
return { title: pkgKey(p), value: pkgKey(p) };
|
||||
})
|
||||
"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
></Select>
|
||||
</OptionRow> -->
|
||||
</OptionRow>
|
||||
<OptionRow title="amfs">
|
||||
<FilePicker
|
||||
:directory="true"
|
||||
@ -119,6 +118,20 @@ const extraDisplayOptionsDisabled = computed(() => {
|
||||
"
|
||||
></FilePicker>
|
||||
</OptionRow>
|
||||
<OptionRow title="mu3io">
|
||||
<Select
|
||||
v-model="prf.current!.sgt.io"
|
||||
placeholder="segatools built-in"
|
||||
:options="[
|
||||
{ title: 'segatools built-in', value: null },
|
||||
...pkg.ios.map((p) => {
|
||||
return { title: pkgKey(p), value: pkgKey(p) };
|
||||
}),
|
||||
]"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
></Select>
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
<OptionCategory title="Display">
|
||||
<OptionRow
|
||||
|
@ -31,6 +31,9 @@ const disabledTooltip = computed(() => {
|
||||
if (prf.current?.sgt.amfs.length === 0) {
|
||||
return 'The amfs path must be specified';
|
||||
}
|
||||
if (prf.current?.sgt.hook === null || prf.current?.sgt.hook === undefined) {
|
||||
return 'A segatools hook package is necessary';
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
|
@ -14,7 +14,10 @@ const install = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
await invoke('install_package', { key: pkgKey(props.pkg) });
|
||||
await invoke('install_package', {
|
||||
key: pkgKey(props.pkg),
|
||||
force: true,
|
||||
});
|
||||
} catch (err) {
|
||||
if (props.pkg !== undefined) {
|
||||
props.pkg.js.busy = false;
|
||||
|
@ -46,8 +46,13 @@ export const usePkgStore = defineStore('pkg', {
|
||||
fromName: (state) => (namespace: string, name: string) =>
|
||||
state.pkg[`${namespace}-${name}`] ?? null,
|
||||
all: (state) => Object.values(state),
|
||||
allLocal: (state) => Object.values(state.pkg).filter((p) => p.loc),
|
||||
allLocal: (state) =>
|
||||
Object.values(state.pkg).filter((p) => p.loc?.kind === 'Mod'),
|
||||
allRemote: (state) => Object.values(state.pkg).filter((p) => p.rmt),
|
||||
hooks: (state) =>
|
||||
Object.values(state.pkg).filter((p) => p.loc?.kind === 'Hook'),
|
||||
ios: (state) =>
|
||||
Object.values(state.pkg).filter((p) => p.loc?.kind === 'IO'),
|
||||
},
|
||||
actions: {
|
||||
setupListeners() {
|
||||
|
@ -7,6 +7,7 @@ export interface Package {
|
||||
version: string;
|
||||
path: string;
|
||||
dependencies: string[];
|
||||
kind: 'Unchecked' | 'Unsupported' | 'Mod' | 'Hook' | 'IO';
|
||||
} | null;
|
||||
rmt: {
|
||||
version: string;
|
||||
@ -28,6 +29,8 @@ export interface ProfileMeta {
|
||||
|
||||
export interface SegatoolsConfig {
|
||||
target: string;
|
||||
hook: string | null;
|
||||
io: string | null;
|
||||
amfs: string;
|
||||
option: string;
|
||||
appdata: string;
|
||||
|
Reference in New Issue
Block a user