From 327d89525865d1a42a7ed1d9d7f718ad2e32f8e0 Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Fri, 31 May 2024 21:53:41 +0200 Subject: [PATCH] chore(notifications): Notifications need to be rewritten .-. --- desktop/src/bridge/notifications.ts | 41 ++++++++- desktop/src/globals.css | 15 ++++ desktop/src/types/core.d.ts | 2 +- .../notifications/NotificationComponent.tsx | 88 ++++++++++++------- .../notifications/NotificationOverlay.tsx | 40 +-------- .../notifications/NotificationPopup.tsx | 29 +++++- desktop/src/ui/hooks/useNotifications.ts | 1 + 7 files changed, 137 insertions(+), 79 deletions(-) diff --git a/desktop/src/bridge/notifications.ts b/desktop/src/bridge/notifications.ts index 152e6f53..7abdb93a 100644 --- a/desktop/src/bridge/notifications.ts +++ b/desktop/src/bridge/notifications.ts @@ -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): Promise { +export async function updateNotification(id: number, notification: Partial): Promise { + 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): Promise { + 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 { } export async function getNotifications(): Promise { - 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 { + return _notifications.find(noti => noti.id === id); +} + export async function on< T extends Core.NotificationEvents, >( diff --git a/desktop/src/globals.css b/desktop/src/globals.css index 9589b4ea..86523798 100644 --- a/desktop/src/globals.css +++ b/desktop/src/globals.css @@ -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; diff --git a/desktop/src/types/core.d.ts b/desktop/src/types/core.d.ts index 64e06935..de52f82e 100644 --- a/desktop/src/types/core.d.ts +++ b/desktop/src/types/core.d.ts @@ -8,5 +8,5 @@ declare namespace Core { progress?: number; } - export type NotificationEvents = 'init' | 'added' | 'removed' | 'cleared'; + export type NotificationEvents = 'init' | 'modified' | 'added' | 'removed' | 'cleared'; } \ No newline at end of file diff --git a/desktop/src/ui/components/overlay/notifications/NotificationComponent.tsx b/desktop/src/ui/components/overlay/notifications/NotificationComponent.tsx index 13cb0435..31c22b37 100644 --- a/desktop/src/ui/components/overlay/notifications/NotificationComponent.tsx +++ b/desktop/src/ui/components/overlay/notifications/NotificationComponent.tsx @@ -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) => 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(true); - const [pausable, setPausable] = createSignal(); - const [interval, setSecondsInterval] = createSignal(); + const [timer, setTimer] = createSignal(); const [visible, setVisible] = createSignal(true); const [secondsLeft, setSecondsLeft] = createSignal(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 ( + //
onEnter()} @@ -106,7 +120,7 @@ function NotificationOverlayComponent(props: NotificationComponentProps) {
- +
+ // ); } @@ -125,31 +140,31 @@ function NotificationPopupComponent(props: NotificationComponentProps) { return (
- {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)}`, })}
- {props.data.title} - {props.data.message} + {props.title} + {props.message}
- +
- +
@@ -160,9 +175,14 @@ function NotificationPopupComponent(props: NotificationComponentProps) { function NotificationComponent(props: NotificationComponentProps) { return ( - <> - {(props.overlay ? NotificationOverlayComponent : NotificationPopupComponent)(props)} - + + + + + + + + ); } diff --git a/desktop/src/ui/components/overlay/notifications/NotificationOverlay.tsx b/desktop/src/ui/components/overlay/notifications/NotificationOverlay.tsx index b6b50a8c..347f5763 100644 --- a/desktop/src/ui/components/overlay/notifications/NotificationOverlay.tsx +++ b/desktop/src/ui/components/overlay/notifications/NotificationOverlay.tsx @@ -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 ( {}} @@ -47,10 +13,10 @@ export default function NotificationOverlay() { mount={document.body} ref={ref => ref.classList.add('fixed', 'bottom-8', 'right-8')} > -
+
{notification => ( - + )}
diff --git a/desktop/src/ui/components/overlay/notifications/NotificationPopup.tsx b/desktop/src/ui/components/overlay/notifications/NotificationPopup.tsx index 3062726e..9fc0f4f5 100644 --- a/desktop/src/ui/components/overlay/notifications/NotificationPopup.tsx +++ b/desktop/src/ui/components/overlay/notifications/NotificationPopup.tsx @@ -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) { {noti => (
- +
)} diff --git a/desktop/src/ui/hooks/useNotifications.ts b/desktop/src/ui/hooks/useNotifications.ts index 6bfe1efe..7d24342e 100644 --- a/desktop/src/ui/hooks/useNotifications.ts +++ b/desktop/src/ui/hooks/useNotifications.ts @@ -21,6 +21,7 @@ function useNotifications(onUpdate?: OnUpdateFn): Signal { manager.on('added', () => update('added')); manager.on('removed', () => update('removed')); manager.on('cleared', () => update('cleared')); + manager.on('modified', () => update('modified')); }); return [notifications, setNotifications];