Files
STARTLINER/src/components/App.vue

332 lines
10 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 { invoke } from '../invoke';
import {
useClientStore,
useGeneralStore,
usePkgStore,
usePrfStore,
} from '../stores';
import { Dirs } from '../types';
import { messageSplit } from '../util';
const pkg = usePkgStore();
const prf = usePrfStore();
const general = useGeneralStore();
const client = useClientStore();
pkg.setupListeners();
const currentTab: Ref<'users' | 'loc' | 'patches' | 'rmt' | 'cfg' | 'info'> =
ref('users');
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 () => {
invoke('list_directories').then((d) => {
general.dirs = d as Dirs;
client.load();
});
const fetch_promise = pkg.fetch(true);
await Promise.all([prf.reloadList(), prf.reload()]);
if (prf.current !== null) {
currentTab.value = 'loc';
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;
});
</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'
"
>
<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="A sad state of affairs"
@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="currentTab"
v-on:update:value="
(value) => {
currentTab = value as any;
}
"
class="h-screen"
>
<div class="fixed w-full flex z-100">
<TabList class="grow" :show-navigators="false">
<Tab value="users"><div class="pi pi-users"></div></Tab>
<Tab :disabled="isProfileDisabled" value="loc"
><div class="pi pi-box"></div
></Tab>
<Tab
v-if="prf.current?.meta.game === 'chunithm'"
value="patches"
><div class="pi pi-ticket"></div
></Tab>
<Tab
v-if="pkg.networkStatus === 'online'"
:disabled="isProfileDisabled"
value="rmt"
><div class="pi pi-download"></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(currentTab)"
>
<InputIcon class="self-center mr-2">
<i class="pi pi-search" />
</InputIcon>
<InputText
v-if="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">
<ModList :search="pkgSearchTerm" />
</TabPanel>
<TabPanel value="rmt">
<ModStore :search="pkgSearchTerm" />
</TabPanel>
<TabPanel value="cfg">
<OptionList />
</TabPanel>
<TabPanel value="users">
<ProfileList />
</TabPanel>
<TabPanel value="patches">
<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>
</TabPanel>
<TabPanel value="info">
<InfoPage />
</TabPanel>
</TabPanels>
<div v-if="currentTab === 'users' || 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;
}
</style>