fix: Ingress, should probably add more event listeners and more ingress types for better user feedback

This commit is contained in:
LynithDev 2024-10-22 22:09:28 +02:00
parent a158f5a058
commit f00db32cab
8 changed files with 98 additions and 110 deletions

View file

@ -23,6 +23,7 @@
"@leeoniya/ufuzzy": "^1.0.14",
"@onelauncher/client": "workspace:*",
"@solid-primitives/i18n": "^2.1.1",
"@solid-primitives/map": "^0.4.13",
"@solid-primitives/refs": "^1.0.8",
"@solidjs/router": "^0.14.7",
"@t3-oss/env-core": "^0.11.0",

View file

@ -1,45 +1,21 @@
import type { IngressPayload, IngressType } from '@onelauncher/client/bindings';
import { PausableTimer } from '@onelauncher/client';
import { InfoCircleIcon } from '@untitled-theme/icons-solid';
import { type ComponentProps, createEffect, createSignal, type JSX, Match, Show, Switch } from 'solid-js';
import { createEffect, createSignal, type JSX, Match, Show, Switch } from 'solid-js';
type NotificationComponentProps = IngressPayload & {
export interface NotificationData {
title: string;
message: string;
fraction?: number | undefined;
}
export type NotificationComponentProps = NotificationData & {
icon?: () => JSX.Element;
overlay: boolean;
};
function IconFromNotificationType(type: IngressType['type']): (props: ComponentProps<'svg'>) => JSX.Element {
switch (type) {
case 'initialize':
case 'download_java':
case 'download_loader':
case 'sync_cluster':
case 'copy_cluster':
case 'sync_config':
case 'archival':
case 'download_package':
case 'download_pack':
default:
return InfoCircleIcon;
}
}
function ColorFromNotificationType(type: IngressType['type']): string {
switch (type) {
case 'initialize':
case 'download_java':
case 'download_loader':
case 'sync_cluster':
case 'copy_cluster':
case 'sync_config':
case 'archival':
case 'download_package':
case 'download_pack':
default:
return 'text-fg-primary';
}
}
const TOTAL_SECONDS = 7;
const fractionEnded = (fraction: number | undefined) => fraction !== undefined && fraction >= 0.97;
function NotificationOverlayComponent(props: NotificationComponentProps) {
const [disappearing, setDisappearing] = createSignal<boolean>(true);
const [timer, setTimer] = createSignal<PausableTimer | undefined>();
@ -49,7 +25,7 @@ function NotificationOverlayComponent(props: NotificationComponentProps) {
createEffect(() => {
setDisappearing(props.fraction === undefined);
if (props.fraction !== null && props.fraction >= 1) {
if (fractionEnded(props.fraction)) {
setVisible(false);
return;
}
@ -65,10 +41,6 @@ function NotificationOverlayComponent(props: NotificationComponentProps) {
}
});
// onMount(() => {
// setVisible(true);
// });
function onInterval() {
if (secondsLeft() <= 0) {
hide();
@ -93,14 +65,6 @@ function NotificationOverlayComponent(props: NotificationComponentProps) {
}
return (
// <Transition
// enterClass="noti-animation-enter"
// enterActiveClass="noti-animation-enter-active"
// enterToClass="noti-animation-enter-to"
// exitClass="noti-animation-leave"
// exitActiveClass="noti-animation-leave-active"
// exitToClass="noti-animation-leave-to"
// >
<Show when={visible()}>
<div
class="flex flex-col overflow-hidden rounded-lg bg-component-bg"
@ -111,7 +75,7 @@ function NotificationOverlayComponent(props: NotificationComponentProps) {
<NotificationPopupComponent {...props} />
</div>
<Show when={disappearing() === true && props.fraction === undefined}>
<Show when={disappearing() === true}>
<div class="h-1.5 w-full bg-brand-disabled">
<div
class="h-1.5 rounded-lg bg-brand transition-width"
@ -123,21 +87,21 @@ function NotificationOverlayComponent(props: NotificationComponentProps) {
</Show>
</div>
</Show>
// </Transition>
);
}
function NotificationPopupComponent(props: NotificationComponentProps) {
// eslint-disable-next-line solid/reactivity -- Doesn't really matter here
const Icon = props.icon ?? InfoCircleIcon;
return (
<div class="flex flex-col gap-y-1 p-2">
<div class="grid grid-cols-[24px_1fr_auto] min-h-10 place-items-center gap-3">
{IconFromNotificationType(props.event.type)({
class: `w-6 h-6 ${ColorFromNotificationType(props.event.type)}`,
})}
<Icon class="h-6 w-6 text-fg-primary" />
<div class="w-full flex flex-col">
<span class={`font-medium ${ColorFromNotificationType(props.event.type)}`}>{props.message}</span>
<span class="text-sm text-white/60">{props.message}</span>
<div class="w-full flex flex-col gap-y-1">
<span class="text-fg-primary font-medium capitalize">{props.title}</span>
<span class="text-sm text-white/60 capitalize">{props.message}</span>
</div>
<Show when={props.overlay !== true}>
@ -147,12 +111,12 @@ function NotificationPopupComponent(props: NotificationComponentProps) {
</Show>
</div>
<Show when={props.fraction !== null}>
<Show when={props.fraction !== undefined && props.fraction !== null && !fractionEnded(props.fraction)}>
<div class="h-1.5 w-full overflow-hidden rounded-full bg-brand-disabled">
<div
class="h-full max-w-full min-w-0 rounded-full bg-brand transition-width"
style={{
width: `${Math.floor(props.fraction!)}%`,
width: `${Math.floor(props.fraction! * 100)}%`,
}}
/>
</div>

View file

@ -1,10 +1,10 @@
import useIngress from '~ui/hooks/useIngress';
import useNotifications from '~ui/hooks/useNotifications';
import { For } from 'solid-js';
import Popup from '../Popup';
import NotificationComponent from './NotificationComponent';
export default function NotificationOverlay() {
const [ingress] = useIngress();
const [notifications] = useNotifications();
return (
<Popup
@ -14,7 +14,7 @@ export default function NotificationOverlay() {
visible={() => true}
>
<div class="flex flex-col-reverse gap-2">
<For each={ingress()}>
<For each={Object.values(notifications())}>
{notification => (
<NotificationComponent overlay {...notification} />
)}

View file

@ -1,37 +1,29 @@
import { Settings01Icon, Trash01Icon } from '@untitled-theme/icons-solid';
import useIngress from '~ui/hooks/useIngress';
import useNotifications from '~ui/hooks/useNotifications';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-solid';
import { For, Match, Switch } from 'solid-js';
import { createEffect, createMemo, For, Match, on, Switch } from 'solid-js';
import Button from '../../base/Button';
import Popup, { type PopupProps } from '../Popup';
import NotificationComponent from './NotificationComponent';
function NotificationPopup(props: PopupProps) {
const [ingress] = useIngress(updateSize);
const [notifications, setNotifications] = useNotifications();
let inner!: HTMLDivElement;
let parent!: HTMLDivElement;
function updateSize() {
createEffect(on(notifications, () => {
if (inner && parent) {
const rect = inner.getBoundingClientRect();
parent.style.height = `${rect.height}px`;
}
}
}));
// onMount(() => {
// document.addEventListener('keypress', async (e) => {
// if (e.key === 'n') {
// const progress = Math.random() > 0.0 ? { progress: 0.39 } : {};
// const id = await manager.addNotification({
// title: 'Test Notification',
// message: 'This is a test notification',
// notification_type: manager.NotificationType.Download,
// ...(progress),
// });
// }
// });
// });
const memoedNotifications = createMemo(() => Object.values(notifications()));
function clearNotifications() {
setNotifications({});
}
return (
<Popup {...props}>
@ -40,10 +32,10 @@ function NotificationPopup(props: PopupProps) {
<div class="flex flex-col items-stretch justify-start gap-2 text-start" ref={inner}>
<p class="px-2 pt-1 text-2lg">Notifications</p>
<Switch>
<Match when={ingress().length > 0}>
<Match when={memoedNotifications().length > 0}>
<OverlayScrollbarsComponent class="max-h-[min(500px,60vh)] overflow-auto">
<div class="flex flex-col-reverse items-stretch justify-center">
<For each={ingress()}>
<For each={memoedNotifications()}>
{noti => (
<div class="w-full flex flex-col">
<NotificationComponent {...noti} overlay={false} />
@ -54,14 +46,14 @@ function NotificationPopup(props: PopupProps) {
</div>
</OverlayScrollbarsComponent>
</Match>
<Match when={ingress().length === 0}>
<Match when>
<span class="px-2">You have no notifications</span>
<span class="h-px bg-gray-05" />
</Match>
</Switch>
<div class="flex flex-row items-end justify-between">
<Button buttonStyle="ghost" iconLeft={<Trash01Icon />} onClick={() => {}}>
<Button buttonStyle="ghost" iconLeft={<Trash01Icon />} onClick={clearNotifications}>
Clear Notifications
</Button>

View file

@ -1,26 +0,0 @@
import { events, type IngressPayload, type IngressType } from '@onelauncher/client/bindings';
import { createSignal, onMount, type Signal } from 'solid-js';
type Ingresses = IngressPayload[];
type OnUpdateFn = (event: IngressType['type']) => unknown;
function useIngress(onUpdate?: OnUpdateFn): Signal<Ingresses> {
const [ingresses, setIngresses] = createSignal<Ingresses>([]);
async function update(event: IngressType['type']) {
const ingress: Ingresses = []; // await manager.getNotifications();
setIngresses(ingress);
if (onUpdate)
onUpdate(event);
}
onMount(() => {
update('initialize');
events.ingressPayload.listen(e => update(e.payload.event.type));
});
return [ingresses, setIngresses];
}
export default useIngress;

View file

@ -0,0 +1,34 @@
import type { NotificationData } from '~ui/components/overlay/notifications/NotificationComponent';
import { events } from '@onelauncher/client/bindings';
import { createSignal, onMount, type Signal } from 'solid-js';
type Notifications = Record<string, NotificationData>;
type OnUpdateFn = () => unknown;
function useNotifications(onUpdate?: OnUpdateFn): Signal<Notifications> {
const [notifications, setNotifications] = createSignal<Notifications>({});
async function update(id: string, data: NotificationData) {
setNotifications((notifications) => {
notifications[id] = data;
return {
...notifications,
};
});
if (onUpdate)
onUpdate();
}
onMount(() => {
events.ingressPayload.listen(e => update(e.payload.ingress_uuid, {
title: e.payload.event.type.replaceAll('_', ' '),
message: e.payload.message,
fraction: e.payload.fraction ?? undefined,
}));
});
return [notifications, setNotifications];
}
export default useNotifications;

View file

@ -235,7 +235,7 @@ pub async fn install_minecraft(
})
.await?;
State::sync().await?;
send_ingress(&ingress, 1.0, Some("installed minecraft successfully")).await?;
send_ingress(&ingress, 100.0, Some("installed minecraft successfully")).await?;
Ok(())
}

23
pnpm-lock.yaml generated
View file

@ -100,6 +100,9 @@ importers:
'@solid-primitives/i18n':
specifier: ^2.1.1
version: 2.1.1(solid-js@1.9.1)
'@solid-primitives/map':
specifier: ^0.4.13
version: 0.4.13(solid-js@1.9.1)
'@solid-primitives/refs':
specifier: ^1.0.8
version: 1.0.8(solid-js@1.9.1)
@ -1265,6 +1268,11 @@ packages:
peerDependencies:
solid-js: ^1.6.12
'@solid-primitives/map@0.4.13':
resolution: {integrity: sha512-B1zyFbsiTQvqPr+cuPCXO72sRuczG9Swncqk5P74NCGw1VE8qa/Ry9GlfI1e/VdeQYHjan+XkbE3rO2GW/qKew==}
peerDependencies:
solid-js: ^1.6.12
'@solid-primitives/memo@1.3.9':
resolution: {integrity: sha512-OIlGcsEPXr6U23fDaSEm8iqezWDT4SNSjLTekWQVOu4qHostnMPlqU8xo1Jf0I7OgRuqA0wTMQ5cu0bYjeR/ZQ==}
peerDependencies:
@ -1285,6 +1293,11 @@ packages:
peerDependencies:
solid-js: ^1.6.12
'@solid-primitives/trigger@1.1.0':
resolution: {integrity: sha512-00BbAiXV66WwjHuKZc3wr0+GLb9C24mMUmi3JdTpNFgHBbrQGrIHubmZDg36c5/7wH+E0GQtOOanwQS063PO+A==}
peerDependencies:
solid-js: ^1.6.12
'@solid-primitives/utils@6.2.3':
resolution: {integrity: sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==}
peerDependencies:
@ -5061,6 +5074,11 @@ snapshots:
dependencies:
solid-js: 1.9.1
'@solid-primitives/map@0.4.13(solid-js@1.9.1)':
dependencies:
'@solid-primitives/trigger': 1.1.0(solid-js@1.9.1)
solid-js: 1.9.1
'@solid-primitives/memo@1.3.9(solid-js@1.9.1)':
dependencies:
'@solid-primitives/scheduled': 1.4.3(solid-js@1.9.1)
@ -5080,6 +5098,11 @@ snapshots:
dependencies:
solid-js: 1.9.1
'@solid-primitives/trigger@1.1.0(solid-js@1.9.1)':
dependencies:
'@solid-primitives/utils': 6.2.3(solid-js@1.9.1)
solid-js: 1.9.1
'@solid-primitives/utils@6.2.3(solid-js@1.9.1)':
dependencies:
solid-js: 1.9.1