forked from akanyan/STARTLINER
441 lines
13 KiB
Vue
441 lines
13 KiB
Vue
<script setup lang="ts">
|
|
import { Ref, computed, onMounted, ref } from 'vue';
|
|
import Button from 'primevue/button';
|
|
import ConfirmDialog from 'primevue/confirmdialog';
|
|
import Dialog from 'primevue/dialog';
|
|
import InputIcon from 'primevue/inputicon';
|
|
import InputText from 'primevue/inputtext';
|
|
import ProgressBar from 'primevue/progressbar';
|
|
import ScrollPanel from 'primevue/scrollpanel';
|
|
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 { listen } from '@tauri-apps/api/event';
|
|
import InfoPage from './InfoPage.vue';
|
|
import ModList from './ModList.vue';
|
|
import ModStore from './ModStore.vue';
|
|
import OptionList from './OptionList.vue';
|
|
import PatchList from './PatchList.vue';
|
|
import ProfileList from './ProfileList.vue';
|
|
import StartButton from './StartButton.vue';
|
|
import {
|
|
useClientStore,
|
|
useGeneralStore,
|
|
usePkgStore,
|
|
usePrfStore,
|
|
} from '../stores';
|
|
import { messageSplit, shouldPreferDark } from '../util';
|
|
|
|
document.documentElement.classList.toggle('use-dark-mode', shouldPreferDark());
|
|
|
|
const pkg = usePkgStore();
|
|
const prf = usePrfStore();
|
|
const general = useGeneralStore();
|
|
const client = useClientStore();
|
|
|
|
client.load();
|
|
|
|
pkg.setupListeners();
|
|
|
|
const pkgSearchTerm = ref('');
|
|
|
|
const isProfileDisabled = computed(() => prf.current === null);
|
|
|
|
const updateProgress: Ref<number | null> = ref(null);
|
|
|
|
listen<number>('update-progress', (ev) => {
|
|
updateProgress.value = Math.floor(ev.payload * 100);
|
|
});
|
|
|
|
listen<undefined>('update-end', (_) => {
|
|
updateProgress.value = null;
|
|
});
|
|
|
|
onMounted(async () => {
|
|
const fetch_promise = pkg.fetch(true);
|
|
|
|
await Promise.all([prf.reloadList(), prf.reload()]);
|
|
|
|
if (prf.current !== null) {
|
|
await pkg.reloadAll();
|
|
}
|
|
|
|
await fetch_promise;
|
|
});
|
|
|
|
const errorVisible = ref(false);
|
|
const errorMessage = ref('No error');
|
|
const errorHeader = ref('No header');
|
|
|
|
listen<{ message: string; header: string }>('invoke-error', (event) => {
|
|
errorVisible.value = true;
|
|
errorMessage.value = event.payload.message;
|
|
errorHeader.value = event.payload.header;
|
|
});
|
|
|
|
listen<string>('launch-error', (event) => {
|
|
errorVisible.value = true;
|
|
errorMessage.value = event.payload;
|
|
errorHeader.value = 'Launch error';
|
|
});
|
|
|
|
interface DownloadingStatus {
|
|
ratio: number;
|
|
pkg_key: string;
|
|
}
|
|
const downloading_status: Ref<DownloadingStatus[]> = ref([]);
|
|
|
|
const download_value = computed(() => {
|
|
return (
|
|
downloading_status.value.map((v) => v.ratio).reduce((a, v) => a * v) *
|
|
100
|
|
);
|
|
});
|
|
|
|
const downloadProgressText = computed(() => {
|
|
if (download_value.value < 7) {
|
|
return '';
|
|
}
|
|
let pkgs = `${downloading_status.value.length} package${downloading_status.value.length === 1 ? '' : 's'}`;
|
|
if (download_value.value < 14) {
|
|
return pkgs;
|
|
} else {
|
|
return `${pkgs} (${Math.floor(download_value.value)}%)`;
|
|
}
|
|
});
|
|
|
|
listen<DownloadingStatus>('download-progress', (event) => {
|
|
let status = downloading_status.value.find(
|
|
(v) => v.pkg_key === event.payload.pkg_key
|
|
);
|
|
|
|
if (status === undefined) {
|
|
status = {
|
|
ratio: 0,
|
|
pkg_key: event.payload.pkg_key,
|
|
};
|
|
downloading_status.value.push(status);
|
|
}
|
|
status.ratio = event.payload.ratio;
|
|
|
|
const remove = () => {
|
|
if (status !== undefined) {
|
|
downloading_status.value = downloading_status.value.filter(
|
|
(v) => v.pkg_key !== event.payload.pkg_key
|
|
);
|
|
}
|
|
};
|
|
|
|
if (status.ratio === 1.0) {
|
|
remove();
|
|
}
|
|
|
|
setTimeout(() => remove, 10_000);
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<main
|
|
:class="
|
|
client.scaleFactor === 's'
|
|
? 'main-scale-s'
|
|
: client.scaleFactor === 'm'
|
|
? 'main-scale-m'
|
|
: client.scaleFactor === 'l'
|
|
? 'main-scale-l'
|
|
: 'main-scale-xl'
|
|
"
|
|
>
|
|
<div
|
|
v-if="downloading_status.length > 0"
|
|
class="download-progress-bg"
|
|
></div>
|
|
<ProgressBar
|
|
v-if="downloading_status.length > 0"
|
|
:value="download_value"
|
|
class="download-progress"
|
|
>{{ downloadProgressText }}</ProgressBar
|
|
>
|
|
<ConfirmDialog>
|
|
<template #message="{ message }">
|
|
<ScrollPanel
|
|
v-if="messageSplit(message).length > 5"
|
|
style="width: 100%; height: 40vh"
|
|
>
|
|
<p v-for="m in messageSplit(message)">
|
|
{{ m }}
|
|
</p></ScrollPanel
|
|
>
|
|
<div v-else>
|
|
<p v-for="m in messageSplit(message)">
|
|
{{ m }}
|
|
</p>
|
|
</div>
|
|
</template>
|
|
</ConfirmDialog>
|
|
<Dialog
|
|
modal
|
|
:visible="errorVisible"
|
|
:closable="false /*this shit doesn't work */"
|
|
:header="errorHeader"
|
|
:style="{ width: '50vw' }"
|
|
>
|
|
<div class="flex flex-col gap-4">
|
|
{{ errorMessage }}
|
|
<Button
|
|
class="m-auto"
|
|
label="OK"
|
|
@click="errorVisible = false"
|
|
/>
|
|
</div>
|
|
</Dialog>
|
|
<Dialog
|
|
modal
|
|
:visible="updateProgress !== null"
|
|
:closable="false"
|
|
header="Updating"
|
|
:style="{ width: '200px' }"
|
|
>
|
|
<ProgressBar :value="updateProgress ?? undefined" />
|
|
</Dialog>
|
|
|
|
<Tabs
|
|
lazy
|
|
:value="client.currentTab"
|
|
v-on:update:value="
|
|
(value) => {
|
|
client.currentTab = value as string;
|
|
client.save();
|
|
}
|
|
"
|
|
class="h-screen"
|
|
>
|
|
<div class="fixed w-full flex z-100">
|
|
<TabList class="grow" :show-navigators="false">
|
|
<Tab value="users"><div class="pi pi-home"></div></Tab>
|
|
<Tab
|
|
:disabled="
|
|
isProfileDisabled || pkg.networkStatus !== 'online'
|
|
"
|
|
value="rmt"
|
|
><div class="pi pi-download"></div
|
|
></Tab>
|
|
<Tab :disabled="isProfileDisabled" value="loc"
|
|
><div class="pi pi-box"></div
|
|
></Tab>
|
|
<Tab
|
|
v-if="(prf.current?.data.sgt.target.length ?? 0) > 0"
|
|
value="patches"
|
|
><div class="pi pi-ticket"></div
|
|
></Tab>
|
|
|
|
<Tab :disabled="isProfileDisabled" value="cfg"
|
|
><div class="pi pi-cog"></div
|
|
></Tab>
|
|
<Tab value="info"
|
|
><div class="pi pi-info-circle"></div
|
|
></Tab>
|
|
|
|
<div class="grow"></div>
|
|
|
|
<div class="flex gap-4">
|
|
<div
|
|
class="flex"
|
|
v-if="
|
|
['loc', 'rmt', 'cfg'].includes(
|
|
client.currentTab
|
|
)
|
|
"
|
|
>
|
|
<InputIcon class="self-center mr-2">
|
|
<i class="pi pi-search" />
|
|
</InputIcon>
|
|
<InputText
|
|
v-if="client.currentTab === 'cfg'"
|
|
style="min-width: 0; width: 25dvw"
|
|
class="self-center"
|
|
size="small"
|
|
placeholder="Search"
|
|
v-model="general.cfgSearchTerm"
|
|
/>
|
|
<InputText
|
|
v-else
|
|
style="min-width: 0; width: 25dvw"
|
|
class="self-center"
|
|
size="small"
|
|
placeholder="Search"
|
|
v-model="pkgSearchTerm"
|
|
/>
|
|
</div>
|
|
<Button
|
|
v-if="pkg.networkStatus === 'connecting'"
|
|
class="shrink self-center"
|
|
icon="pi pi-sync pi-spin"
|
|
size="small"
|
|
:disabled="true"
|
|
/>
|
|
<Button
|
|
v-if="
|
|
pkg.networkStatus === 'offline' &&
|
|
!client.offlineMode
|
|
"
|
|
class="shrink self-center"
|
|
icon="pi pi-sync"
|
|
size="small"
|
|
@click="pkg.fetch(false)"
|
|
/>
|
|
<Button
|
|
v-if="
|
|
pkg.networkStatus === 'online' &&
|
|
pkg.hasAvailableUpdates
|
|
"
|
|
icon="pi pi-download"
|
|
label="UPDATE ALL"
|
|
size="small"
|
|
class="mr-4 m-2.5"
|
|
@click="pkg.updateAll()"
|
|
></Button>
|
|
</div>
|
|
<div class="grow"></div>
|
|
<StartButton />
|
|
</TabList>
|
|
</div>
|
|
<TabPanels class="w-full grow mt-[3rem]">
|
|
<TabPanel value="loc" v-if="!isProfileDisabled">
|
|
<ModList :search="pkgSearchTerm" />
|
|
</TabPanel>
|
|
<TabPanel value="rmt" v-if="!isProfileDisabled">
|
|
<ModStore :search="pkgSearchTerm" />
|
|
</TabPanel>
|
|
<TabPanel value="cfg" v-if="!isProfileDisabled">
|
|
<OptionList />
|
|
</TabPanel>
|
|
<TabPanel value="users">
|
|
<ProfileList />
|
|
</TabPanel>
|
|
<TabPanel value="patches" v-if="!isProfileDisabled">
|
|
<PatchList
|
|
v-if="
|
|
pkg.hasLocal('mempatcher-mempatcher') &&
|
|
prf.isPkgKeyEnabled('mempatcher-mempatcher')
|
|
.value === true
|
|
"
|
|
/>
|
|
<div v-else>
|
|
Patches require <code>mempatcher</code> to be installed
|
|
and enabled.
|
|
|
|
<div>
|
|
<Button
|
|
label="Add mempatcher"
|
|
icon="pi pi-plus"
|
|
class="mt-3"
|
|
@click="
|
|
() =>
|
|
pkg.installFromKey(
|
|
'mempatcher-mempatcher'
|
|
)
|
|
"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</TabPanel>
|
|
<TabPanel value="info">
|
|
<InfoPage />
|
|
</TabPanel>
|
|
</TabPanels>
|
|
<div
|
|
v-if="
|
|
client.currentTab === 'users' ||
|
|
client.currentTab === 'info'
|
|
"
|
|
>
|
|
<img
|
|
v-if="prf.current?.meta.game === 'ongeki'"
|
|
src="/sticker-ongeki.svg"
|
|
class="fixed bottom-0 right-0 z-999"
|
|
/>
|
|
<img
|
|
v-else-if="prf.current?.meta.game === 'chunithm'"
|
|
src="/sticker-chunithm.svg"
|
|
class="fixed bottom-0 right-0 z-999"
|
|
/>
|
|
</div>
|
|
</Tabs>
|
|
</main>
|
|
</template>
|
|
|
|
<style lang="css">
|
|
@import 'tailwindcss';
|
|
@import 'primeicons/primeicons.css';
|
|
|
|
.p-tablist-tab-list {
|
|
height: 3rem;
|
|
}
|
|
|
|
html,
|
|
body {
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.main-scale-s {
|
|
zoom: 1;
|
|
}
|
|
|
|
.main-scale-m {
|
|
zoom: 1.25;
|
|
}
|
|
|
|
.main-scale-l {
|
|
zoom: 1.4;
|
|
}
|
|
|
|
.main-scale-xl {
|
|
zoom: 1.7;
|
|
}
|
|
|
|
.p-tablist {
|
|
background-color: #ffffff !important;
|
|
border-bottom: white 10px !important;
|
|
}
|
|
|
|
.p-tab {
|
|
border-bottom: none !important;
|
|
}
|
|
.p-tablist-active-bar {
|
|
display: none !important;
|
|
}
|
|
|
|
.p-tooltip {
|
|
min-width: 300px;
|
|
}
|
|
|
|
.p-progressbar,
|
|
.p-progressbar-value,
|
|
.p-progressbar-label {
|
|
transition-duration: 0s !important;
|
|
}
|
|
|
|
.download-progress {
|
|
position: fixed !important;
|
|
bottom: 0;
|
|
left: 5vw;
|
|
width: 90vw;
|
|
z-index: 10000 !important;
|
|
margin: 20px auto;
|
|
}
|
|
.download-progress-bg {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 60px;
|
|
background-color: var(--p-surface-900);
|
|
border-top: 1px solid var(--p-surface-600);
|
|
z-index: 998;
|
|
}
|
|
</style>
|