feat: start checks

This commit is contained in:
2025-03-16 17:55:38 +00:00
parent 08d6a2a2fe
commit 8d55e92fc9
26 changed files with 456 additions and 211 deletions

View File

@ -38,8 +38,8 @@ onMounted(async () => {
await Promise.all([prf.reloadList(), prf.reload()]);
if (prf.current !== null) {
await pkg.reloadAll();
currentTab.value = 0;
await pkg.reloadAll();
}
fetch_promise.then(async () => {
@ -65,7 +65,7 @@ onMounted(async () => {
><div class="pi pi-list-check"></div
></Tab>
<Tab
v-if="!pkg.offline"
v-if="pkg.networkStatus === 'online'"
:disabled="isProfileDisabled"
:value="1"
><div class="pi pi-download"></div
@ -98,7 +98,14 @@ onMounted(async () => {
/>
</div>
<Button
v-if="pkg.offline"
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'"
class="shrink self-center"
icon="pi pi-sync"
size="small"
@ -151,6 +158,7 @@ onMounted(async () => {
<style lang="css">
@import 'tailwindcss';
@import 'primeicons/primeicons.css';
.p-tablist-tab-list {
height: 3rem;

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import Button from 'primevue/button';
import { open } from '@tauri-apps/plugin-shell';
import { Package } from '../types';
defineProps({
pkg: Object as () => Package,
});
</script>
<template>
<Button
rounded
icon="pi pi-external-link"
severity="info"
aria-label="storepage"
size="small"
class="self-center ml-2"
style="width: 2rem; height: 2rem"
:disabled="!pkg?.rmt"
v-on:click="open(pkg!.rmt!.package_url)"
/>
</template>

View File

@ -1,21 +1,25 @@
<script setup lang="ts">
import { ref } from 'vue';
import { computed, ref } from 'vue';
import Button from 'primevue/button';
import Fieldset from 'primevue/fieldset';
import ModListEntry from './ModListEntry.vue';
import { usePkgStore } from '../stores';
import ModTitlecard from './ModTitlecard.vue';
import { usePkgStore, usePrfStore } from '../stores';
import { Package } from '../types';
const props = defineProps({
search: String,
});
const pkg = usePkgStore();
const pkgs = usePkgStore();
const prf = usePrfStore();
const empty = ref(true);
const group = () => {
const a = Object.assign(
{},
Object.groupBy(
pkg.allLocal
pkgs.allLocal
.filter(
(p) =>
props.search === undefined ||
@ -34,9 +38,36 @@ const group = () => {
empty.value = Object.keys(a).length === 0;
return a;
};
const missing = computed(() => {
return prf.current?.mods.filter((m) => !pkgs.hasLocal(m));
});
</script>
<template>
<Fieldset legend="Missing" v-if="(missing?.length ?? 0) > 0">
<div class="flex items-center" v-for="p in missing">
<ModTitlecard
show-namespace
:pkg="
{
namespace: p.split('-')[0],
name: p.split('-')[1],
} as Package
"
/>
<Button
rounded
icon="pi pi-minus"
severity="danger"
aria-label="install"
size="small"
class="self-center ml-4"
style="width: 2rem; height: 2rem"
v-on:click="prf.togglePkg(p, false)"
/>
</div>
</Fieldset>
<Fieldset v-for="(namespace, key) in group()" :legend="key.toString()">
<ModListEntry v-for="p in namespace" :pkg="p" />
</Fieldset>

View File

@ -4,12 +4,14 @@ import Button from 'primevue/button';
import ToggleSwitch from 'primevue/toggleswitch';
import { open } from '@tauri-apps/plugin-shell';
import InstallButton from './InstallButton.vue';
import LinkButton from './LinkButton.vue';
import ModTitlecard from './ModTitlecard.vue';
import UpdateButton from './UpdateButton.vue';
import { usePrfStore } from '../stores';
import { usePkgStore, usePrfStore } from '../stores';
import { Package } from '../types';
const prf = usePrfStore();
const pkgs = usePkgStore();
const props = defineProps({
pkg: Object as () => Package,
@ -27,13 +29,14 @@ const model = computed({
<template>
<div class="flex items-center">
<ModTitlecard showVersion :pkg="pkg" />
<ModTitlecard show-version show-icon show-description :pkg="pkg" />
<UpdateButton :pkg="pkg" />
<!-- @vue-expect-error Can't 'as any' because it breaks VSCode -->
<ToggleSwitch
v-if="pkg?.loc?.kind === 'Mod' || pkg?.loc?.kind === 'Unsupported'"
class="scale-[1.33] shrink-0"
inputId="switch"
:disabled="!pkg?.loc"
:disabled="pkg!.loc!.kind === 'Unsupported'"
v-model="model"
/>
<InstallButton :pkg="pkg" />
@ -47,17 +50,7 @@ const model = computed({
style="width: 2rem; height: 2rem"
v-on:click="pkg?.loc && open(pkg.loc.path ?? '')"
/>
<Button
v-if="pkg?.rmt"
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(pkg.rmt.package_url ?? '')"
/>
<LinkButton v-if="pkgs.networkStatus === 'online'" :pkg="pkg" />
</div>
</template>

View File

@ -33,14 +33,18 @@ const list = () => {
<template>
<div class="flex gap-4 items-center">
<div class="flex flex-col gap-2">
<div class="flex gap-2">
<div class="grow">Show installed</div>
<ToggleSwitch v-model="pkgs.showInstalled" />
</div>
<div class="flex gap-2">
<div class="text-amber-400 grow">Show deprecated</div>
<ToggleSwitch v-model="pkgs.showDeprecated" />
</div>
<div class="flex gap-2">
<!-- <div class="flex gap-2">
<div class="text-red-400 grow">Show NSFW</div>
<ToggleSwitch v-model="pkgs.showNSFW" />
</div>
</div> -->
</div>
<div class="flex flex-col gap-2 grow">
<MultiSelect
@ -67,7 +71,3 @@ const list = () => {
</div>
<div v-if="empty" class="text-3xl"></div>
</template>
<style lang="scss">
@import 'primeicons/primeicons.css';
</style>

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
import Button from 'primevue/button';
import { open } from '@tauri-apps/plugin-shell';
import InstallButton from './InstallButton.vue';
import LinkButton from './LinkButton.vue';
import ModTitlecard from './ModTitlecard.vue';
import { Package } from '../types';
@ -11,21 +10,13 @@ defineProps({
</script>
<template>
<ModTitlecard :pkg="pkg" show-namespace show-categories />
<InstallButton :pkg="pkg" />
<Button
rounded
icon="pi pi-external-link"
severity="info"
aria-label="storepage"
size="small"
class="self-center ml-2"
style="width: 2rem; height: 2rem"
:disabled="!pkg?.rmt"
v-on:click="open(pkg?.rmt?.package_url ?? '')"
<ModTitlecard
:pkg="pkg"
show-namespace
show-categories
show-icon
show-description
/>
<InstallButton :pkg="pkg" />
<LinkButton :pkg="pkg" />
</template>
<style lang="scss">
@import 'primeicons/primeicons.css';
</style>

View File

@ -9,6 +9,8 @@ const props = defineProps({
showNamespace: Boolean,
showVersion: Boolean,
showCategories: Boolean,
showDescription: Boolean,
showIcon: Boolean,
});
const iconSrc = () => {
@ -26,12 +28,13 @@ const iconSrc = () => {
<template>
<img
v-if="showIcon"
:src="iconSrc()"
class="self-center rounded-sm"
width="32px"
height="32px"
/>
<label class="m-3 align-middle text grow z-5 h-50px" for="switch">
<label class="m-3 align-middle text grow z-5 h-50px">
<div>
<span class="text-lg">
{{ pkg?.name ?? 'Untitled' }}
@ -48,6 +51,18 @@ const iconSrc = () => {
class="pi pi-exclamation-triangle ml-1 text-red-400"
>
</span>
<span
v-if="pkg?.loc?.kind === 'Hook'"
v-tooltip="'Hook'"
class="pi pi-wrench ml-1 text-blue-400"
>
</span>
<span
v-if="pkg?.loc?.kind === 'IO'"
v-tooltip="'IO'"
class="pi pi-wrench ml-1 text-green-400"
>
</span>
<span
v-if="showNamespace && pkg?.namespace"
class="text-sm opacity-75"
@ -69,8 +84,8 @@ const iconSrc = () => {
>
</span>
</div>
<div class="text-sm opacity-75">
{{ pkg?.description ?? 'No description' }}
<div v-if="showDescription" class="text-sm opacity-75">
{{ pkg?.description || 'No description' }}
</div>
<div v-if="showCategories" class="mt-1 flex gap-1">
<span class="text-xs" v-for="c in pkg?.rmt?.categories"

View File

@ -250,12 +250,12 @@ const extraDisplayOptionsDisabled = computed(() => {
"
></FilePicker>
</OptionRow>
<OptionRow
<!-- <OptionRow
v-if="prf.current!.network.network_type == 'Artemis'"
title="ARTEMiS console"
>
<ToggleSwitch v-model="prf.current!.network.local_console" />
</OptionRow>
</OptionRow> -->
<OptionRow
v-if="prf.current!.network.network_type == 'Remote'"
title="Server address"

View File

@ -1,20 +1,52 @@
<script setup lang="ts">
import { Ref, computed, ref } from 'vue';
import Button from 'primevue/button';
import ConfirmDialog from 'primevue/confirmdialog';
import ScrollPanel from 'primevue/scrollpanel';
import { useConfirm } from 'primevue/useconfirm';
import { listen } from '@tauri-apps/api/event';
import { invoke } from '../invoke';
import { usePrfStore } from '../stores';
const prf = usePrfStore();
const confirmDialog = useConfirm();
type StartStatus = 'ready' | 'preparing' | 'running';
const startStatus: Ref<StartStatus> = ref('ready');
const startline = async () => {
const startline = async (force: boolean) => {
startStatus.value = 'preparing';
if (!force) {
const start_check: object[] = await invoke('start_check');
if (start_check.length > 0) {
const message = start_check.map((o) => {
if ('MissingRemotePackage' in o) {
return `Package missing: ${o.MissingRemotePackage}`;
} else if ('MissingLocalPackage' in o) {
return `Package missing: ${o.MissingLocalPackage}`;
} else if ('MissingDependency' in o) {
return `Dependency missing: ${o.MissingDependency}`;
} else if ('MissingTool' in o) {
return `Tool missing: ${o.MissingTool}`;
} else {
return 'Unknown error';
}
});
confirmDialog.require({
message: message.join('\n'),
header: 'Start check failed',
accept: () => {
startline(true);
},
});
startStatus.value = 'ready';
return;
}
}
try {
await invoke('startline');
} catch (e) {
} catch (_) {
startStatus.value = 'ready';
}
};
@ -44,9 +76,52 @@ listen('launch-start', () => {
listen('launch-end', () => {
startStatus.value = 'ready';
});
const messageSplit = (message: any) => {
return message.message?.split('\n');
};
</script>
<template>
<ConfirmDialog>
<template #container="{ message, acceptCallback, rejectCallback }">
<div
class="flex flex-col p-8 bg-surface-0 dark:bg-surface-900 rounded"
>
<span class="font-bold self-center text-2xl block mb-2 mt-2">{{
message.header
}}</span>
<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>
<div class="flex self-center items-center gap-2 mt-6">
<Button
label="Run anyway"
@click="acceptCallback"
size="small"
class="w-32"
></Button>
<Button
label="Cancel"
outlined
size="small"
@click="rejectCallback"
class="w-32"
></Button>
</div>
</div>
</template>
</ConfirmDialog>
<Button
v-if="startStatus === 'ready'"
v-tooltip="disabledTooltip"
@ -56,7 +131,7 @@ listen('launch-end', () => {
aria-label="start"
size="small"
class="m-2.5"
@click="startline()"
@click="startline(false)"
/>
<Button
v-else-if="startStatus === 'preparing'"

View File

@ -3,6 +3,7 @@ import { createPinia } from 'pinia';
import { definePreset } from '@primevue/themes';
import Theme from '@primevue/themes/aura';
import PrimeVue from 'primevue/config';
import ConfirmationService from 'primevue/confirmationservice';
import Tooltip from 'primevue/tooltip';
import App from './components/App.vue';
import { changePrimaryColor } from './util';
@ -18,6 +19,7 @@ app.use(PrimeVue, {
preset: Preset,
},
});
app.use(ConfirmationService);
changePrimaryColor(null);
app.directive('tooltip', Tooltip);
app.mount('#app');

View File

@ -57,7 +57,8 @@ export const useGeneralStore = defineStore('general', () => {
export const usePkgStore = defineStore('pkg', {
state: (): {
pkg: { [key: string]: Package };
offline: boolean;
networkStatus: 'connecting' | 'offline' | 'online';
showInstalled: boolean;
showDeprecated: boolean;
showNSFW: boolean;
availableCategories: Set<string>;
@ -66,7 +67,8 @@ export const usePkgStore = defineStore('pkg', {
} => {
return {
pkg: {},
offline: false,
networkStatus: 'connecting',
showInstalled: false,
showDeprecated: false,
showNSFW: false,
availableCategories: new Set(),
@ -79,12 +81,14 @@ 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?.kind === 'Mod'),
allLocal: (state) => Object.values(state.pkg).filter((p) => p.loc),
hasLocal: (state) => (key: string) =>
key in state.pkg && state.pkg[key].loc,
allRemote: (state) =>
Object.values(state.pkg).filter(
(p) =>
p.rmt !== null &&
(state.showInstalled || !p.loc) &&
(state.showDeprecated || !p.rmt.deprecated) &&
(state.showNSFW || !p.rmt.nsfw) &&
(state.includeCategories.length === 0 ||
@ -154,15 +158,16 @@ export const usePkgStore = defineStore('pkg', {
},
async fetch(nopopup: boolean) {
this.networkStatus = 'connecting';
try {
if (nopopup) {
await invoke_nopopup('fetch_listings');
} else {
await invoke('fetch_listings');
}
this.offline = false;
this.networkStatus = 'online';
} catch (e) {
this.offline = true;
this.networkStatus = 'offline';
return;
}
await this.reloadAll();
@ -243,11 +248,17 @@ export const usePrfStore = defineStore('prf', () => {
list.value = (await invoke('list_profiles')) as ProfileMeta[];
};
const togglePkg = async (pkg: Package | undefined, enable: boolean) => {
const togglePkg = async (
pkg: Package | string | undefined,
enable: boolean
) => {
if (pkg === undefined) {
return;
}
await invoke('toggle_package', { key: pkgKey(pkg), enable });
await invoke('toggle_package', {
key: typeof pkg === 'string' ? pkg : pkgKey(pkg),
enable,
});
await reload();
};