feat: initial support for segatools pkgs

This commit is contained in:
2025-03-15 00:08:33 +01:00
parent b525e74467
commit caa20a3aa0
20 changed files with 246 additions and 112 deletions

View File

@ -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.

View File

@ -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) {

View File

@ -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>

View File

@ -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">

View File

@ -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

View File

@ -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;
});

View File

@ -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;

View File

@ -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() {

View File

@ -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;