feat: new error banner, qol

This commit is contained in:
2025-04-04 22:14:09 +00:00
parent ca871f069f
commit 93e0a7e313
7 changed files with 112 additions and 26 deletions

View File

@ -63,7 +63,7 @@ pub async fn startline(app: AppHandle, refresh: bool) -> Result<(), String> {
if let Some(p) = &mut appd.profile { if let Some(p) = &mut appd.profile {
log::debug!("{}", hash); log::debug!("{}", hash);
p.line_up(hash, refresh, app.clone()).await p.line_up(hash, refresh, app.clone()).await
.map_err(|e| format!("Lineup failed:\n{}", e))?; .map_err(|e| e.to_string())?;
let app_clone = app.clone(); let app_clone = app.clone();
let p_clone = p.clone(); let p_clone = p.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {

View File

@ -88,7 +88,9 @@ impl Display {
settings.borrow_mut().resolution = Resolution::new(width, height); settings.borrow_mut().resolution = Resolution::new(width, height);
} }
display_set.apply()?; display_set.apply().map_err(
|_| anyhow!("The selected monitor has been disconnected or doesn't support the chosen display mode")
)?;
displayz::refresh()?; displayz::refresh()?;
log::debug!("prepare display: done"); log::debug!("prepare display: done");

View File

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Ref, computed, onMounted, ref } from 'vue'; import { Ref, computed, onMounted, ref } from 'vue';
import Button from 'primevue/button'; import Button from 'primevue/button';
import Dialog from 'primevue/dialog';
import InputIcon from 'primevue/inputicon'; import InputIcon from 'primevue/inputicon';
import InputText from 'primevue/inputtext'; import InputText from 'primevue/inputtext';
import Tab from 'primevue/tab'; import Tab from 'primevue/tab';
@ -8,13 +9,19 @@ import TabList from 'primevue/tablist';
import TabPanel from 'primevue/tabpanel'; import TabPanel from 'primevue/tabpanel';
import TabPanels from 'primevue/tabpanels'; import TabPanels from 'primevue/tabpanels';
import Tabs from 'primevue/tabs'; import Tabs from 'primevue/tabs';
import { listen } from '@tauri-apps/api/event';
import ModList from './ModList.vue'; import ModList from './ModList.vue';
import ModStore from './ModStore.vue'; import ModStore from './ModStore.vue';
import OptionList from './OptionList.vue'; import OptionList from './OptionList.vue';
import ProfileList from './ProfileList.vue'; import ProfileList from './ProfileList.vue';
import StartButton from './StartButton.vue'; import StartButton from './StartButton.vue';
import { invoke } from '../invoke'; import { invoke } from '../invoke';
import { useClientStore, useGeneralStore, usePkgStore, usePrfStore } from '../stores'; import {
useClientStore,
useGeneralStore,
usePkgStore,
usePrfStore,
} from '../stores';
import { Dirs } from '../types'; import { Dirs } from '../types';
const pkg = usePkgStore(); const pkg = usePkgStore();
@ -56,35 +63,81 @@ onMounted(async () => {
}); });
}); });
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> </script>
<template> <template>
<main :class="client.scaleFactor === 's' ? 'main-scale-s' : client.scaleFactor === 'm' ? 'main-scale-m' : client.scaleFactor === 'l' ? 'main-scale-l' : 'main-scale-xl'"> <main
:class="
client.scaleFactor === 's'
? 'main-scale-s'
: client.scaleFactor === 'm'
? 'main-scale-m'
: client.scaleFactor === 'l'
? 'main-scale-l'
: 'main-scale-xl'
"
>
<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>
<Tabs <Tabs
lazy lazy
:value="currentTab" :value="currentTab"
v-on:update:value="(value) => { currentTab = value; }" v-on:update:value="
(value) => {
currentTab = value;
}
"
class="h-screen" class="h-screen"
> >
<div class="fixed w-full flex z-100"> <div class="fixed w-full flex z-100">
<TabList class="grow" :show-navigators="false"> <TabList class="grow" :show-navigators="false">
<Tab :value="3" <Tab :value="3"
><div class="pi pi-users"></div ><div class="pi pi-users" v-tooltip="'Profiles'"></div
></Tab> ></Tab>
<Tab :disabled="isProfileDisabled" :value="0" <Tab :disabled="isProfileDisabled" :value="0"
><div class="pi pi-box"></div ><div
class="pi pi-box"
v-tooltip="'Installed packages'"
></div
></Tab> ></Tab>
<Tab v-if="prf.current?.meta.game === 'chunithm'" :value="4" <Tab v-if="prf.current?.meta.game === 'chunithm'" :value="4"
><div class="pi pi-ticket"></div ><div class="pi pi-ticket" v-tooltip="'Patches'"></div
></Tab> ></Tab>
<Tab <Tab
v-if="pkg.networkStatus === 'online'" v-if="pkg.networkStatus === 'online'"
:disabled="isProfileDisabled" :disabled="isProfileDisabled"
:value="1" :value="1"
><div class="pi pi-download"></div ><div
class="pi pi-download"
v-tooltip="'Package store'"
></div
></Tab> ></Tab>
<Tab :disabled="isProfileDisabled" :value="2" <Tab :disabled="isProfileDisabled" :value="2"
><div class="pi pi-cog"></div ><div class="pi pi-cog" v-tooltip="'Settings'"></div
></Tab> ></Tab>
<div class="grow"></div> <div class="grow"></div>
@ -96,7 +149,7 @@ onMounted(async () => {
</InputIcon> </InputIcon>
<InputText <InputText
v-if="currentTab === 2" v-if="currentTab === 2"
style="min-width: 0; width: 25dvw;" style="min-width: 0; width: 25dvw"
class="self-center" class="self-center"
size="small" size="small"
placeholder="Search" placeholder="Search"
@ -104,7 +157,7 @@ onMounted(async () => {
/> />
<InputText <InputText
v-else v-else
style="min-width: 0; width: 25dvw;" style="min-width: 0; width: 25dvw"
class="self-center" class="self-center"
size="small" size="small"
placeholder="Search" placeholder="Search"
@ -119,14 +172,20 @@ onMounted(async () => {
:disabled="true" :disabled="true"
/> />
<Button <Button
v-if="pkg.networkStatus === 'offline' && !client.offlineMode" v-if="
pkg.networkStatus === 'offline' &&
!client.offlineMode
"
class="shrink self-center" class="shrink self-center"
icon="pi pi-sync" icon="pi pi-sync"
size="small" size="small"
@click="pkg.fetch(false)" @click="pkg.fetch(false)"
/> />
<Button <Button
v-if="pkg.networkStatus === 'online' && pkg.hasAvailableUpdates" v-if="
pkg.networkStatus === 'online' &&
pkg.hasAvailableUpdates
"
icon="pi pi-download" icon="pi pi-download"
label="UPDATE ALL" label="UPDATE ALL"
size="small" size="small"
@ -161,8 +220,13 @@ onMounted(async () => {
</footer> </footer>
</TabPanel> </TabPanel>
<TabPanel :value="4"> <TabPanel :value="4">
CHUNITHM patches are not yet implemented.<br />Use CHUNITHM patches are not implemented yet.<br />Use
<a href=https://patcher.two-torial.xyz/ target="_blank" style="text-decoration: underline;">patcher.two-torial.xyz</a> <a
href="https://patcher.two-torial.xyz/"
target="_blank"
style="text-decoration: underline"
>patcher.two-torial.xyz</a
>
</TabPanel> </TabPanel>
</TabPanels> </TabPanels>
<div v-if="currentTab === 5 || currentTab === 3"> <div v-if="currentTab === 5 || currentTab === 3">
@ -196,19 +260,19 @@ body {
} }
.main-scale-s { .main-scale-s {
zoom: 1.0 zoom: 1;
} }
.main-scale-m { .main-scale-m {
zoom: 1.25 zoom: 1.25;
} }
.main-scale-l { .main-scale-l {
zoom: 1.4 zoom: 1.4;
} }
.main-scale-xl { .main-scale-xl {
zoom: 1.7 zoom: 1.7;
} }
.p-tablist { .p-tablist {

View File

@ -1,7 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import Button from 'primevue/button'; import Button from 'primevue/button';
import InputText from 'primevue/inputtext'; import InputText from 'primevue/inputtext';
import * as path from '@tauri-apps/api/path';
import { open } from '@tauri-apps/plugin-dialog'; import { open } from '@tauri-apps/plugin-dialog';
import { usePrfStore } from '../stores';
const props = defineProps({ const props = defineProps({
placeholder: String, placeholder: String,
@ -12,10 +14,25 @@ const props = defineProps({
callback: Function, callback: Function,
}); });
const prf = usePrfStore();
const filePick = async () => { const filePick = async () => {
const exePath = prf.current?.data.sgt.target;
let defaultPath: string | undefined;
if (
exePath !== undefined &&
exePath.length > 0 &&
props.value !== undefined &&
!(await path.isAbsolute(props.value))
) {
defaultPath = await path.join(exePath, '..');
defaultPath = await path.join(defaultPath, props.value);
defaultPath = await path.join(defaultPath, '..');
}
const res = await open({ const res = await open({
multiple: false, multiple: false,
directory: props.directory, directory: props.directory,
defaultPath,
filters: filters:
props.promptname && props.extension props.promptname && props.extension
? [ ? [
@ -28,7 +45,7 @@ const filePick = async () => {
}); });
if (res != null && props.callback !== undefined) { if (res != null && props.callback !== undefined) {
props.callback(res); props.callback(res);
/*path.relative(cfgs.current?.data.exe_dir ?? '', res) */ /*path.relative(prf.current?.data.sgt.target ?? '', res) */
} }
}; };
</script> </script>

View File

@ -7,6 +7,9 @@ const prf = usePrfStore();
</script> </script>
<template> <template>
<div v-if="prf.list.length === 0">
Welcome to STARTLINER! Start by creating a profile.
</div>
<div class="mt-4 flex flex-row flex-wrap align-middle gap-4"> <div class="mt-4 flex flex-row flex-wrap align-middle gap-4">
<Button <Button
label="O.N.G.E.K.I. profile" label="O.N.G.E.K.I. profile"

View File

@ -45,7 +45,7 @@ const updatesModel = computed({
</OptionRow> </OptionRow>
<OptionRow <OptionRow
title="Offline mode" title="Offline mode"
tooltip="Disables the package store. Applies after a restart" tooltip="Disables the package store. Requires a restart."
> >
<ToggleSwitch v-model="offlineModel" /> <ToggleSwitch v-model="offlineModel" />
</OptionRow> </OptionRow>

View File

@ -3,7 +3,7 @@ import {
InvokeOptions, InvokeOptions,
invoke as real_invoke, invoke as real_invoke,
} from '@tauri-apps/api/core'; } from '@tauri-apps/api/core';
import { message } from '@tauri-apps/plugin-dialog'; import { emit } from '@tauri-apps/api/event';
export const invoke = async <T>( export const invoke = async <T>(
cmd: string, cmd: string,
@ -14,9 +14,9 @@ export const invoke = async <T>(
return await real_invoke(cmd, args, options); return await real_invoke(cmd, args, options);
} catch (e: unknown) { } catch (e: unknown) {
if (typeof e === 'string') { if (typeof e === 'string') {
await message(`${cmd}: ${e}`, { emit('invoke-error', {
title: `Error`, message: e,
kind: 'error', header: `${cmd} failed`,
}); });
} else { } else {
console.error(`Unresolved error: ${e}`); console.error(`Unresolved error: ${e}`);