import { useReduxStore } from "@/modules/redux/hooks/base"; import { BuildKey, useIsArrayExtended } from "@/utilities"; import { faBug, faCircleNotch, faExclamationTriangle, faInfoCircle, faStream, IconDefinition, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon, FontAwesomeIconProps, } from "@fortawesome/react-fontawesome"; import { Fragment, FunctionComponent, ReactNode, useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { Button, Dropdown, Overlay, ProgressBar, Tooltip, } from "react-bootstrap"; import { useDidUpdate, useTimeoutWhen } from "rooks"; enum State { Idle, Working, Failed, } function useTotalProgress(progress: Site.Progress[]) { return useMemo(() => { const { value, count } = progress.reduce( (prev, { value, count }) => { prev.value += value; prev.count += count; return prev; }, { value: 0, count: 0 } ); if (count === 0) { return 0; } else { return (value + 0.001) / count; } }, [progress]); } function useHasErrorNotification(notifications: Server.Notification[]) { return useMemo( () => notifications.find((v) => v.type !== "info") !== undefined, [notifications] ); } const NotificationCenter: FunctionComponent = () => { const { progress, notifications, notifier } = useReduxStore((s) => s.site); const dropdownRef = useRef(null); const [hasNew, setHasNew] = useState(false); const hasNewProgress = useIsArrayExtended(progress); const hasNewNotifications = useIsArrayExtended(notifications); useDidUpdate(() => { if (hasNewNotifications || hasNewProgress) { setHasNew(true); } }, [hasNewProgress, hasNewNotifications]); useDidUpdate(() => { if (progress.length === 0 && notifications.length === 0) { setHasNew(false); } }, [progress.length, notifications.length]); const [btnState, setBtnState] = useState(State.Idle); const totalProgress = useTotalProgress(progress); const hasError = useHasErrorNotification(notifications); useEffect(() => { if (hasError) { setBtnState(State.Failed); } else if (totalProgress > 0 && totalProgress < 1.0) { setBtnState(State.Working); } else { setBtnState(State.Idle); } }, [totalProgress, hasError]); const iconProps = useMemo(() => { switch (btnState) { case State.Idle: return { icon: faStream, }; case State.Working: return { icon: faCircleNotch, spin: true, }; default: return { icon: faExclamationTriangle, }; } }, [btnState]); const content = useMemo(() => { const nodes: JSX.Element[] = []; nodes.push( {notifications.length > 0 ? "Notifications" : "No Notifications"} ); nodes.push( ...notifications.map((v, idx) => ( )) ); nodes.push(); nodes.push( {progress.length > 0 ? "Background Tasks" : "No Background Tasks"} ); nodes.push( ...progress.map((v, idx) => ( )) ); return nodes; }, [progress, notifications]); const onToggleClick = useCallback(() => { setHasNew(false); }, []); // Tooltip Controller const [showTooltip, setTooltip] = useState(false); useTimeoutWhen(() => setTooltip(false), 3 * 1000, showTooltip); useDidUpdate(() => { if (notifier.content) { setTooltip(true); } }, [notifier.timestamp]); return ( {content} {(props) => { return ( {notifier.content} ); }} ); }; const Notification: FunctionComponent = ({ type, message, }) => { const icon = useMemo(() => { switch (type) { case "info": return faInfoCircle; case "warning": return faExclamationTriangle; default: return faBug; } }, [type]); return (
{message}
); }; const Progress: FunctionComponent = ({ name, value, count, header, }) => { const isCompleted = value / count > 1; const displayValue = Math.min(count, value + 1); return (

{header}

{isCompleted ? "Completed successfully" : name}

); }; export default NotificationCenter;