initial commit

This commit is contained in:
2025-02-12 22:13:31 +01:00
commit 047b2e9f4a
52 changed files with 8552 additions and 0 deletions

136
src/components/App.vue Normal file
View File

@ -0,0 +1,136 @@
<script setup lang="ts">
import { Ref, onMounted, ref } from 'vue';
import { updatePrimaryPalette } from '@primevue/themes';
import Button from 'primevue/button';
import Tab from 'primevue/tab';
import TabList from 'primevue/tablist';
import TabPanel from 'primevue/tabpanel';
import TabPanels from 'primevue/tabpanels';
import Tabs from 'primevue/tabs';
import { invoke } from '@tauri-apps/api/core';
import { open } from '@tauri-apps/plugin-dialog';
import ModList from './ModList.vue';
import ModStore from './ModStore.vue';
import Options from './Options.vue';
import { Profile } from '../types';
const changePrimaryColor = (game: 'ongeki' | 'chunithm') => {
const color = game === 'ongeki' ? 'pink' : 'yellow';
updatePrimaryPalette({
50: `{${color}.50}`,
100: `{${color}.100}`,
200: `{${color}.200}`,
300: `{${color}.300}`,
400: `{${color}.400}`,
500: `{${color}.500}`,
600: `{${color}.600}`,
700: `{${color}.700}`,
800: `{${color}.800}`,
900: `{${color}.900}`,
950: `{${color}.950}`,
});
};
let profile: Ref<Profile | null> = ref(null);
let key = ref(0);
const loadProfile = async () => {
profile = await invoke('get_current_profile');
if (profile === null) {
const file = await open({
multiple: false,
directory: false,
filters: [
{
name: 'mu3.exe' /* or chusanApp.exe'*/,
extensions: ['exe'],
},
],
});
if (file !== null) {
profile = await invoke('init_profile', { path: file });
}
}
key.value += 1;
};
const isDisabled = () => profile === null;
onMounted(async () => {
await loadProfile();
});
changePrimaryColor('ongeki');
</script>
<template>
<main>
<Tabs lazy value="3" class="h-screen">
<div class="fixed w-full flex z-100">
<TabList class="grow">
<Tab :disabled="isDisabled()" :key="key" value="0"
><div class="pi pi-list-check"></div
></Tab>
<Tab :disabled="isDisabled()" :key="key" value="1"
><div class="pi pi-download"></div
></Tab>
<Tab :disabled="isDisabled()" :key="key" value="2"
><div class="pi pi-cog"></div
></Tab>
<Tab value="3"
><div class="pi pi-question-circle"></div
></Tab>
<div class="grow"></div>
<Button
disabled
icon="pi pi-play"
label="START"
aria-label="start"
size="small"
class="m-2.5"
/>
</TabList>
</div>
<TabPanels class="w-full grow mt-[3rem]">
<TabPanel value="0">
<ModList :profile="profile!" />
</TabPanel>
<TabPanel value="1">
<ModStore />
</TabPanel>
<TabPanel value="2">
<Options />
</TabPanel>
<TabPanel value="3">
UNDER CONSTRUCTION<br /><br />
<Button
label="Create profile"
icon="pi pi-plus"
aria-label="open-executable"
@click="loadProfile()"
/>
<img
src="/sticker-ongeki.svg"
class="fixed bottom-0 right-0"
/>
</TabPanel>
</TabPanels>
</Tabs>
</main>
</template>
<style lang="css">
@import 'tailwindcss';
.p-tablist-tab-list {
height: 3rem;
}
html,
body {
margin: 0;
padding: 0;
}
</style>

View File

@ -0,0 +1,32 @@
<script setup lang="ts">
import { Reactive, onMounted, reactive } from 'vue';
import Fieldset from 'primevue/fieldset';
import { invoke } from '@tauri-apps/api/core';
import ModListEntry from './ModListEntry.vue';
import { ModEntry, Profile } from '../types';
const mods: Reactive<{ [key: string]: ModEntry[] }> = reactive({});
const props = defineProps({
profile: Object as () => Profile,
});
onMounted(async () => {
const modsRaw: ModEntry[] = await invoke('get_packages');
modsRaw.forEach((m) => {
if (props.profile?.mods.includes(`${m.namespace}-${m.name}`)) {
m.enabled = true;
}
});
Object.assign(
mods,
Object.groupBy(modsRaw, ({ namespace }) => namespace)
);
});
</script>
<template>
<Fieldset v-for="(namespace, key) in mods" :legend="key.toString()">
<ModListEntry v-for="m in namespace" :mod="m" />
</Fieldset>
</template>

View File

@ -0,0 +1,54 @@
<script setup lang="ts">
import Button from 'primevue/button';
import ToggleSwitch from 'primevue/toggleswitch';
import { open } from '@tauri-apps/plugin-shell';
import ModTitlecard from './ModTitlecard.vue';
import { ModEntry } from '../types';
defineProps({
mod: Object as () => ModEntry,
});
</script>
<template>
<div class="flex items-center">
<ModTitlecard showVersion :localIcon="true" :mod="mod" />
<ToggleSwitch
class="scale-[1.33] shrink-0"
inputId="switch"
:modelValue="mod?.enabled"
/>
<Button
rounded
disabled
icon="pi pi-trash"
severity="danger"
aria-label="delete"
size="small"
class="ml-5 self-center shrink-0"
style="width: 2rem; height: 2rem"
/>
<Button
rounded
icon="pi pi-folder"
severity="help"
aria-label="delete"
size="small"
class="ml-2 shrink-0"
style="width: 2rem; height: 2rem"
v-on:click="open(mod?.path ?? '')"
/>
<Button
rounded
icon="pi pi-external-link"
severity="info"
aria-label="delete"
size="small"
class="ml-2 shrink-0"
style="width: 2rem; height: 2rem"
v-on:click="open(mod?.package_url ?? '')"
/>
</div>
</template>
<style></style>

View File

@ -0,0 +1,38 @@
<script setup lang="ts">
import { Reactive, onMounted, reactive } from 'vue';
import { invoke } from '@tauri-apps/api/core';
import ModStoreEntry from './ModStoreEntry.vue';
import { ModEntry } from '../types';
const local: Reactive<{ [key: string]: ModEntry }> = reactive({});
const listings: Reactive<ModEntry[]> = reactive([]);
const reload = async () => {
const modsRaw: ModEntry[] = await invoke('get_packages');
Object.keys(local).forEach((key) => {
delete local[key];
});
for (const m of modsRaw) {
local[`${m.namespace}-${m.name}`] = m;
}
};
onMounted(async () => {
Object.assign(listings, await invoke('get_listings'));
reload();
});
</script>
<template>
<div v-for="l in listings" class="flex flex-row">
<ModStoreEntry
:mod="l"
:isLocal="local[`${l.namespace}-${l.name}`] !== undefined"
v-on:updated="reload()"
/>
</div>
</template>
<style lang="scss">
@import 'primeicons/primeicons.css';
</style>

View File

@ -0,0 +1,60 @@
<script setup lang="ts">
import { ref } from 'vue';
import Button from 'primevue/button';
import { invoke } from '@tauri-apps/api/core';
import { open } from '@tauri-apps/plugin-shell';
import ModTitlecard from './ModTitlecard.vue';
import { ModEntry } from '../types';
const emit = defineEmits(['updated']);
const props = defineProps({
mod: Object as () => ModEntry,
isLocal: Boolean,
});
const isLoading = ref(false);
const handlePackageButton = async () => {
isLoading.value = true;
if (!props.isLocal) {
await invoke('download_package', { pkg: props.mod });
} else {
await invoke('delete_package', {
namespace: props.mod?.namespace,
name: props.mod?.name,
});
}
await invoke('reload_packages');
emit('updated');
isLoading.value = false;
};
</script>
<template>
<ModTitlecard :mod="mod" showNamespace />
<Button
rounded
:icon="isLocal ? 'pi pi-trash' : 'pi pi-plus'"
:severity="isLocal ? 'danger' : 'success'"
aria-label="install"
size="small"
class="self-center"
style="width: 2rem; height: 2rem"
:loading="isLoading"
v-on:click="handlePackageButton()"
/>
<Button
rounded
icon="pi pi-external-link"
severity="info"
aria-label="storepage"
size="small"
class="self-center ml-2"
style="width: 2rem; height: 2rem"
v-on:click="open(mod?.package_url ?? '')"
/>
</template>
<style lang="scss">
@import 'primeicons/primeicons.css';
</style>

View File

@ -0,0 +1,43 @@
<script setup lang="ts">
import { convertFileSrc } from '@tauri-apps/api/core';
import { ModEntry } from '../types';
defineProps({
mod: Object as () => ModEntry,
modelValue: Boolean,
localIcon: Boolean,
showNamespace: Boolean,
showVersion: Boolean,
});
</script>
<template>
<img
:src="localIcon ? convertFileSrc(mod?.icon ?? '') : mod?.icon"
class="self-center rounded-sm"
width="32px"
height="32px"
/>
<label class="m-3 align-middle text grow z-5 h-50px" for="switch">
<div>
<span class="text-lg">
{{ mod?.name ?? 'Untitled' }}
</span>
<span
v-if="showNamespace && mod?.namespace"
class="text-sm opacity-75"
>
by&nbsp;{{ mod.namespace }}
</span>
<span
v-if="showVersion && mod?.version"
class="text-sm opacity-75 m-2"
>
{{ mod.version ?? '?.?.?' }}
</span>
</div>
<div class="text-sm opacity-75">
{{ mod?.description ?? 'No description' }}
</div>
</label>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import Fieldset from 'primevue/fieldset';
import Toggle from 'primevue/toggleswitch';
</script>
<template>
<Fieldset>
<div class="flex w-full flex-col">
<label for="switch" class="flex flex-row w-full p-2">
<div class="grow">Aime emulation</div>
<Toggle disabled inputId="switch" />
</label>
<div class="flex flex-row w-full p-2">
<div class="grow">Enable console</div>
<Toggle disabled />
</div>
</div>
</Fieldset>
</template>