chore(notifications): Notifications need to be rewritten .-.
This commit is contained in:
parent
f2a1ae682b
commit
327d895258
7 changed files with 137 additions and 79 deletions
|
|
@ -2,6 +2,12 @@ import { invoke } from '@tauri-apps/api/core';
|
|||
import type { UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
|
||||
// TODO: Refactor notifications
|
||||
// - getNotifications should return a list of notifications
|
||||
// - event "update" + "add" + "remove" should emit the event AS WELL AS emitting
|
||||
// the ID of the notification / the entire notification data
|
||||
// so that the frontend can easily find the notification reference and update it without losing reactivity
|
||||
|
||||
export enum NotificationType {
|
||||
Info = 'Info',
|
||||
Alert = 'Alert',
|
||||
|
|
@ -28,13 +34,38 @@ class _Emitter {
|
|||
|
||||
const emitter = new _Emitter();
|
||||
|
||||
export async function addNotification(notification: MakeOptional<Core.Notification, 'id' | 'created_at'>): Promise<void> {
|
||||
export async function updateNotification(id: number, notification: Partial<Core.Notification>): Promise<void> {
|
||||
const index = _notifications.findIndex(noti => noti.id === id);
|
||||
if (index < 0) {
|
||||
console.log('no index');
|
||||
return;
|
||||
}
|
||||
|
||||
const found = _notifications[index];
|
||||
if (!found) {
|
||||
console.log('not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const newNoti = {
|
||||
...found,
|
||||
...notification,
|
||||
};
|
||||
|
||||
_notifications[index] = newNoti;
|
||||
emitter.emit('modified');
|
||||
}
|
||||
|
||||
export async function addNotification(notification: MakeOptional<Core.Notification, 'id' | 'created_at'>): Promise<number> {
|
||||
const id = notification.id || _notifications.length + 1;
|
||||
_notifications.push({
|
||||
id: _notifications.length + 1,
|
||||
id,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
...notification,
|
||||
});
|
||||
emitter.emit('added');
|
||||
return id;
|
||||
|
||||
// await invoke('plugin:onelauncher|notifications_add', {
|
||||
// notification: {
|
||||
// created_at: Math.floor(Date.now() / 1000),
|
||||
|
|
@ -64,10 +95,14 @@ export async function clearNotifications(): Promise<void> {
|
|||
}
|
||||
|
||||
export async function getNotifications(): Promise<Core.Notification[]> {
|
||||
return [..._notifications];
|
||||
return [..._notifications]; // Destructured to mock the way backend <-> frontend works. (new list gets sent)
|
||||
// return await invoke('plugin:onelauncher|notifications_get');
|
||||
}
|
||||
|
||||
export async function getNotificationById(id: number): Promise<Core.Notification | undefined> {
|
||||
return _notifications.find(noti => noti.id === id);
|
||||
}
|
||||
|
||||
export async function on<
|
||||
T extends Core.NotificationEvents,
|
||||
>(
|
||||
|
|
|
|||
|
|
@ -58,6 +58,21 @@ h4 { @apply text-lg font-medium; }
|
|||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.noti-animation-enter-active, .noti-animation-leave-active {
|
||||
transition: opacity 160ms, transform 175ms;
|
||||
transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
.noti-animation-enter, .noti-animation-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(75px);
|
||||
}
|
||||
|
||||
.noti-animation-enter-to, .noti-animation-leave {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
body.reduce-motion * {
|
||||
transition-duration: 0.0001ms !important;
|
||||
animation-duration: 0.0001ms !important;
|
||||
|
|
|
|||
2
desktop/src/types/core.d.ts
vendored
2
desktop/src/types/core.d.ts
vendored
|
|
@ -8,5 +8,5 @@ declare namespace Core {
|
|||
progress?: number;
|
||||
}
|
||||
|
||||
export type NotificationEvents = 'init' | 'added' | 'removed' | 'cleared';
|
||||
export type NotificationEvents = 'init' | 'modified' | 'added' | 'removed' | 'cleared';
|
||||
}
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
import { type JSX, Show, createEffect, createSignal } from 'solid-js';
|
||||
import { type JSX, Match, Show, Switch, createEffect, createSignal } from 'solid-js';
|
||||
import { AlertTriangleIcon, CheckCircleIcon, FolderCheckIcon, FolderDownloadIcon, FolderXIcon, InfoCircleIcon, RefreshCcw02Icon } from '@untitled-theme/icons-solid';
|
||||
import TimeAgo from '../../TimeAgo';
|
||||
import { NotificationType } from '../../../../bridge/notifications';
|
||||
import { PausableTimer } from '~utils/PausableTimer';
|
||||
|
||||
interface NotificationComponentProps {
|
||||
type NotificationComponentProps = Core.Notification & {
|
||||
overlay: boolean;
|
||||
data: Core.Notification;
|
||||
};
|
||||
|
||||
function IconFromNotificationType(type: NotificationType): (props: JSX.HTMLAttributes<HTMLDivElement>) => JSX.Element {
|
||||
|
|
@ -49,53 +48,68 @@ function ColorFromNotificationType(type: NotificationType): string {
|
|||
}
|
||||
}
|
||||
|
||||
const TOTAL_SECONDS = 7.5;
|
||||
// TODO(refactor): Use only an interval for this (Remove the pausable signal and in the interval, check if elapsed is longer than duraihon)
|
||||
const TOTAL_SECONDS = 7;
|
||||
function NotificationOverlayComponent(props: NotificationComponentProps) {
|
||||
const [disappearing, setDisappearing] = createSignal<boolean>(true);
|
||||
const [pausable, setPausable] = createSignal<PausableTimer | undefined>();
|
||||
const [interval, setSecondsInterval] = createSignal<PausableTimer | undefined>();
|
||||
const [timer, setTimer] = createSignal<PausableTimer | undefined>();
|
||||
const [visible, setVisible] = createSignal<boolean>(true);
|
||||
const [secondsLeft, setSecondsLeft] = createSignal<number>(TOTAL_SECONDS);
|
||||
|
||||
createEffect(() => {
|
||||
setDisappearing(props.data.progress === undefined || props.data.progress > 1);
|
||||
setDisappearing(props.progress === undefined);
|
||||
|
||||
if (props.progress !== undefined && props.progress >= 1) {
|
||||
setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (disappearing()) {
|
||||
initTimers();
|
||||
setTimer(new PausableTimer(onInterval, 1000, true));
|
||||
return;
|
||||
}
|
||||
else if (pausable()) {
|
||||
clearTimeout(pausable()?.timeout);
|
||||
setPausable(undefined);
|
||||
|
||||
if (timer()) {
|
||||
timer()?.stop();
|
||||
setTimer(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
function initTimers() {
|
||||
setPausable(new PausableTimer(hide, (TOTAL_SECONDS + 0.5) * 1000));
|
||||
setSecondsInterval(new PausableTimer(onInterval, 1000, true));
|
||||
}
|
||||
// onMount(() => {
|
||||
// setVisible(true);
|
||||
// });
|
||||
|
||||
function onInterval() {
|
||||
if (secondsLeft() <= 0) {
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
|
||||
setSecondsLeft(secondsLeft() - 1);
|
||||
}
|
||||
|
||||
function hide() {
|
||||
timer()?.stop();
|
||||
setVisible(false);
|
||||
interval()?.stop();
|
||||
setSecondsInterval(undefined);
|
||||
setTimer(undefined);
|
||||
}
|
||||
|
||||
function onEnter() {
|
||||
pausable()?.pause();
|
||||
interval()?.pause();
|
||||
timer()?.pause();
|
||||
}
|
||||
|
||||
function onLeave() {
|
||||
pausable()?.resume();
|
||||
interval()?.resume();
|
||||
timer()?.resume();
|
||||
}
|
||||
|
||||
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
|
||||
onMouseEnter={() => onEnter()}
|
||||
|
|
@ -106,7 +120,7 @@ function NotificationOverlayComponent(props: NotificationComponentProps) {
|
|||
<NotificationPopupComponent {...props} />
|
||||
</div>
|
||||
|
||||
<Show when={disappearing()}>
|
||||
<Show when={disappearing() === true && props.progress === undefined}>
|
||||
<div class="w-full h-1.5 bg-brand-disabled">
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -118,6 +132,7 @@ function NotificationOverlayComponent(props: NotificationComponentProps) {
|
|||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
// </Transition>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -125,31 +140,31 @@ function NotificationPopupComponent(props: NotificationComponentProps) {
|
|||
return (
|
||||
<div class="p-2 flex flex-col gap-y-1">
|
||||
<div class="min-h-10 grid place-items-center grid-cols-[24px_1fr_auto] gap-3">
|
||||
{IconFromNotificationType(props.data.notification_type)({
|
||||
class: `w-6 h-6 ${ColorFromNotificationType(props.data.notification_type)}`,
|
||||
{IconFromNotificationType(props.notification_type)({
|
||||
class: `w-6 h-6 ${ColorFromNotificationType(props.notification_type)}`,
|
||||
})}
|
||||
|
||||
<div class="flex flex-col w-full">
|
||||
<span class={`font-medium ${ColorFromNotificationType(props.data.notification_type)}`}>{props.data.title}</span>
|
||||
<span class="text-sm text-white/60">{props.data.message}</span>
|
||||
<span class={`font-medium ${ColorFromNotificationType(props.notification_type)}`}>{props.title}</span>
|
||||
<span class="text-sm text-white/60">{props.message}</span>
|
||||
</div>
|
||||
|
||||
<Show when={props.overlay !== true}>
|
||||
<div class="flex flex-row justify-end items-center gap-1">
|
||||
<span class="text-sm text-white/40">
|
||||
<TimeAgo timestamp={props.data.created_at * 1000} />
|
||||
<TimeAgo timestamp={props.created_at * 1000} />
|
||||
</span>
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-brand" />
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Show when={props.data.progress}>
|
||||
<Show when={props.progress !== undefined}>
|
||||
<div class="rounded-full overflow-hidden h-1.5 bg-brand-disabled w-full">
|
||||
<div
|
||||
class="rounded-full h-full min-w-0 max-w-full bg-brand transition-[width]"
|
||||
class="rounded-full h-full min-w-0 max-w-full bg-brand transition-width"
|
||||
style={{
|
||||
width: `${Math.floor(props.data.progress! * 100)}%`,
|
||||
width: `${Math.floor(props.progress! * 100)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -160,9 +175,14 @@ function NotificationPopupComponent(props: NotificationComponentProps) {
|
|||
|
||||
function NotificationComponent(props: NotificationComponentProps) {
|
||||
return (
|
||||
<>
|
||||
{(props.overlay ? NotificationOverlayComponent : NotificationPopupComponent)(props)}
|
||||
</>
|
||||
<Switch>
|
||||
<Match when={props.overlay === true}>
|
||||
<NotificationOverlayComponent {...props} />
|
||||
</Match>
|
||||
<Match when={props.overlay !== true}>
|
||||
<NotificationPopupComponent {...props} />
|
||||
</Match>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,45 +1,11 @@
|
|||
import { For, createSignal } from 'solid-js';
|
||||
import { For } from 'solid-js';
|
||||
import Popup from '../Popup';
|
||||
import NotificationComponent from './NotificationComponent';
|
||||
import useNotifications from '~ui/hooks/useNotifications';
|
||||
import createPausableTimer from '~utils/PausableTimer';
|
||||
|
||||
export default function NotificationOverlay() {
|
||||
const [notifications] = useNotifications();
|
||||
|
||||
// function onAdded() {
|
||||
// const noti = notifications()[notifications().length - 1];
|
||||
// if (noti) {
|
||||
// displayed().push(noti);
|
||||
// setDisplayed([...displayed()]);
|
||||
// const id = noti.id;
|
||||
|
||||
// const pausable = createPausableTimer(() => {
|
||||
// if (!notifications().includes(noti))
|
||||
// return;
|
||||
|
||||
// const index = displayed().findIndex(noti => noti.id === id);
|
||||
|
||||
// if (index > -1) {
|
||||
// displayed().splice(index, 1);
|
||||
// setDisplayed([...displayed()]);
|
||||
// }
|
||||
// }, 2500);
|
||||
// }
|
||||
// }
|
||||
|
||||
// function onRemoved() {
|
||||
// const index = displayed().findIndex(noti => !notifications().includes(noti));
|
||||
// if (index > -1) {
|
||||
// displayed().splice(index, 1);
|
||||
// setDisplayed([...displayed()]);
|
||||
// }
|
||||
// }
|
||||
|
||||
// function onClear() {
|
||||
// setDisplayed([]);
|
||||
// }
|
||||
|
||||
return (
|
||||
<Popup
|
||||
setVisible={() => {}}
|
||||
|
|
@ -47,10 +13,10 @@ export default function NotificationOverlay() {
|
|||
mount={document.body}
|
||||
ref={ref => ref.classList.add('fixed', 'bottom-8', 'right-8')}
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col-reverse gap-2">
|
||||
<For each={notifications()}>
|
||||
{notification => (
|
||||
<NotificationComponent overlay data={notification} />
|
||||
<NotificationComponent overlay {...notification} />
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -21,14 +21,35 @@ function NotificationPopup(props: Popup.PopupProps) {
|
|||
}
|
||||
|
||||
onMount(() => {
|
||||
document.addEventListener('keypress', (e) => {
|
||||
document.addEventListener('keypress', async (e) => {
|
||||
if (e.key === 'n') {
|
||||
manager.addNotification({
|
||||
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,
|
||||
...(Math.random() > 0.7 ? { progress: 0.39 } : {}),
|
||||
...(progress),
|
||||
});
|
||||
|
||||
// if (progress.progress) {
|
||||
setTimeout(() => {
|
||||
manager.updateNotification(id, {
|
||||
progress: 0.5,
|
||||
});
|
||||
}, 3500);
|
||||
|
||||
setTimeout(() => {
|
||||
manager.updateNotification(id, {
|
||||
progress: 0.75,
|
||||
});
|
||||
}, 8500);
|
||||
|
||||
setTimeout(() => {
|
||||
manager.updateNotification(id, {
|
||||
progress: 1,
|
||||
});
|
||||
}, 13500);
|
||||
// }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -46,7 +67,7 @@ function NotificationPopup(props: Popup.PopupProps) {
|
|||
<For each={notifications()}>
|
||||
{noti => (
|
||||
<div class="flex flex-col w-full">
|
||||
<NotificationComponent data={noti} overlay={false} />
|
||||
<NotificationComponent {...noti} overlay={false} />
|
||||
<span class="bg-gray-05 h-px w-full" />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ function useNotifications(onUpdate?: OnUpdateFn): Signal<Notifications> {
|
|||
manager.on('added', () => update('added'));
|
||||
manager.on('removed', () => update('removed'));
|
||||
manager.on('cleared', () => update('cleared'));
|
||||
manager.on('modified', () => update('modified'));
|
||||
});
|
||||
|
||||
return [notifications, setNotifications];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue