mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
notification system
This commit is contained in:
@@ -8,7 +8,7 @@ import { SettingsPage } from "./pages/settings-page.component";
|
||||
import { BsmProgressBar } from "./components/progress-bar/bsm-progress-bar.component";
|
||||
import { useEffect } from "react";
|
||||
import { ThemeService } from "./services/theme.service";
|
||||
import { ThemeConfig } from "./config/default-configuration.config";
|
||||
import { NotificationOverlay } from "./components/notification/notification-overlay.component";
|
||||
|
||||
export default function App() {
|
||||
|
||||
@@ -26,6 +26,7 @@ export default function App() {
|
||||
<div className="relative w-screen h-screen overflow-hidden flex bg-light-main-color-1 dark:bg-main-color-1 z-0 max-w-full">
|
||||
<Modal/>
|
||||
<NavBar/>
|
||||
<NotificationOverlay/>
|
||||
<div className="relative flex flex-col grow max-w-full min-w-0">
|
||||
<TitleBar/>
|
||||
<div className="bg-light-main-color-2 dark:bg-main-color-2 relative rounded-tl-lg grow overflow-hidden max-w-full">
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { NotificationResult, NotificationType, Notification } from "renderer/services/notification.service"
|
||||
import { motion } from "framer-motion"
|
||||
import { BsmImage } from "../shared/bsm-image.component";
|
||||
import BeatRunningImg from "../../../../assets/images/apngs/beat-running.png";
|
||||
import BeatConflictImg from "../../../../assets/images/apngs/beat-conflict.png";
|
||||
import BeatWaitingImg from "../../../../assets/images/apngs/beat-waiting.png";
|
||||
import BeatImpatientImg from "../../../../assets/images/apngs/beat-impatient.png";
|
||||
import { BsmButton } from "../shared/bsm-button.component";
|
||||
|
||||
export function NotificationItem({resolver, index, notification}: {resolver?: (value: NotificationResult|string) => void, index: number, notification: Notification}) {
|
||||
|
||||
const renderImage = () => {
|
||||
if(notification.type === NotificationType.SUCCESS){ return <BsmImage className="h-14" image={BeatRunningImg}/>; }
|
||||
if(notification.type === NotificationType.WARNING){ return <BsmImage className="h-14" image={BeatWaitingImg}/>; }
|
||||
if(notification.type === NotificationType.ERROR){ return <BsmImage className="h-14" image={BeatConflictImg}/>; }
|
||||
return <BsmImage className="h-14" image={BeatImpatientImg}/>
|
||||
}
|
||||
|
||||
const renderNeonColors = () => {
|
||||
if(notification.type === NotificationType.SUCCESS){ return "bg-green-400 shadow-green-400"; }
|
||||
if(notification.type === NotificationType.WARNING){ return "bg-yellow-400 shadow-yellow-400"; }
|
||||
if(notification.type === NotificationType.ERROR){ return "bg-red-500 shadow-red-500"; }
|
||||
return "bg-gray-800 shadow-gray-800 dark:bg-white dark:shadow-white";
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div layout initial={{x: "110%"}} animate={{x: "0%"}} exit={{x:"110%"}} className="relative w-60 rounded-md overflow-hidden -left-64 mb-4 shadow-md shadow-black bg-gradient-to-br from-light-main-color-3 to-light-main-color-2 dark:from-main-color-3 dark:to-main-color-2 text-gray-800 dark:text-white">
|
||||
<div className="w-60 flex flex-col">
|
||||
<div className="flex items-center justify-start pl-1">
|
||||
{renderImage()}
|
||||
<div className="flex flex-col py-2">
|
||||
<h1 className="font-bold leading-5 tracking-wide pr-5">{notification.title}</h1>
|
||||
{notification.desc && <p className="text-sm leading-4">{notification.desc}</p>}
|
||||
</div>
|
||||
</div>
|
||||
{notification.actions && (
|
||||
<div className="flex pb-1 w-full flex-wrap px-[2px]">
|
||||
{notification.actions.map(a => <span className={`px-1 flex-1 grow rounded-md text-center bg-blue-500 hover:brightness-110 transition-all text-gray-200 whitespace-nowrap m-[3px] ${a.cancel && "!bg-gray-500"}`}>{a.title}</span>)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className={`absolute w-full h-1 shadow-center left-0 top-0 ${renderNeonColors()}`}/>
|
||||
<BsmButton icon="cross" className="absolute top-2 right-2" withBar={false}/>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook"
|
||||
import { NotificationService } from "renderer/services/notification.service"
|
||||
import { NotificationItem } from "./notification-item.component";
|
||||
import { AnimatePresence, AnimateSharedLayout, motion } from "framer-motion";
|
||||
|
||||
export function NotificationOverlay() {
|
||||
|
||||
const notificationService = NotificationService.getInstance();
|
||||
|
||||
const notifications = useObservable(notificationService.notifications$);
|
||||
|
||||
|
||||
return (
|
||||
<motion.ul className="absolute h-full w-0 top-0 right-0 z-40 bg-red-500 pt-10">
|
||||
<AnimatePresence>
|
||||
{notifications?.map((n, index) => <NotificationItem key={n.id} index={index} resolver={n.resolver} notification={n.notification}/>)}
|
||||
</AnimatePresence>
|
||||
</motion.ul>
|
||||
)
|
||||
}
|
||||
@@ -7,8 +7,9 @@ import { TerminalIcon } from "./terminal-icon.component";
|
||||
import { DesktopIcon } from "./desktop-icon.component";
|
||||
import { OculusIcon } from "./oculus-icon.component";
|
||||
import { AddIcon } from "./add-icon.component";
|
||||
import CrossIcon from "./cross-icon.component";
|
||||
|
||||
export type BsmIconType = "settings"|"trash"|"favorite"|"folder"|"bsNote"|"terminal"|"desktop"|"oculus"|"add";
|
||||
export type BsmIconType = "settings"|"trash"|"favorite"|"folder"|"bsNote"|"terminal"|"desktop"|"oculus"|"add"|"cross";
|
||||
|
||||
export function BsmIcon({className, icon}: {className?: string, icon: BsmIconType}) {
|
||||
|
||||
@@ -22,6 +23,7 @@ export function BsmIcon({className, icon}: {className?: string, icon: BsmIconTyp
|
||||
if(icon === "desktop"){ return <DesktopIcon className={className}/> }
|
||||
if(icon === "oculus"){ return <OculusIcon className={className}/> }
|
||||
if(icon === "add"){ return <AddIcon className={className}/> }
|
||||
if(icon === "cross"){ return <CrossIcon className={className}/> }
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export default function CrossIcon(props: {className: string}){
|
||||
return (
|
||||
<svg className={props.className} aria-hidden="false" width="12" height="12" viewBox="0 0 12 12">
|
||||
<polygon fill="currentColor" fillRule="evenodd" points="11 1.576 6.583 6 11 10.424 10.424 11 6 6.583 1.576 11 1 10.424 5.417 6 1 1.576 1.576 1 6 5.417 10.424 1"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export class NotificationService{
|
||||
|
||||
private static instance: NotificationService;
|
||||
|
||||
public readonly notifications$: BehaviorSubject<ResolvableNotification[]>;
|
||||
|
||||
public static getInstance(): NotificationService{
|
||||
if(!NotificationService.instance){ NotificationService.instance = new NotificationService(); }
|
||||
return NotificationService.instance;
|
||||
}
|
||||
|
||||
private constructor(){
|
||||
this.notifications$ = new BehaviorSubject<ResolvableNotification[]>([]);
|
||||
|
||||
setInterval(() => {
|
||||
this.notify({title: "coucou le testaze aze azeazeaze", desc: "ceci est un test de notification ne rien faire", actions:[{id: "1",title: "Continuer"}, {id: "1",title: "Faire autre"}, {id: "1",title: "Annuler", cancel: true}]});
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
public notify(notification: Notification): Promise<NotificationResult|string>{
|
||||
let resovableNotification: ResolvableNotification;
|
||||
const promise = new Promise<NotificationResult|string>(resolve => {
|
||||
resovableNotification = {id: uuidv4(), notification: notification, resolver: resolve }
|
||||
setTimeout(() => resolve(NotificationResult.NO_CHOICE), notification.duration || 10000);
|
||||
});
|
||||
|
||||
this.notifications$.next([...this.notifications$.value, resovableNotification]);
|
||||
|
||||
promise.then(() => {
|
||||
this.notifications$.next(this.notifications$.value.filter(n => n.id !== resovableNotification.id));
|
||||
})
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
title: string,
|
||||
type?: NotificationType
|
||||
desc?: string,
|
||||
actions?: NotificationAction[],
|
||||
duration?: number,
|
||||
}
|
||||
|
||||
export enum NotificationType {
|
||||
SUCCESS = 0,
|
||||
WARNING = 1,
|
||||
ERROR = 2,
|
||||
}
|
||||
|
||||
export interface NotificationAction {
|
||||
id: string,
|
||||
title: string,
|
||||
cancel?: boolean
|
||||
}
|
||||
|
||||
export enum NotificationResult {
|
||||
NO_CHOICE = "no_choice",
|
||||
CLOSE = "close",
|
||||
}
|
||||
|
||||
interface ResolvableNotification{
|
||||
id: string,
|
||||
resolver: (value: NotificationResult|string) => void,
|
||||
notification: Notification
|
||||
}
|
||||
Reference in New Issue
Block a user