waveterm/frontend/app/notification/usenotification.tsx
2024-11-15 21:26:16 -08:00

136 lines
4.4 KiB
TypeScript

// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { atoms, getApi } from "@/store/global";
import { useAtom, useAtomValue } from "jotai";
import { useCallback, useEffect, useState } from "react";
const notificationActions: { [key: string]: () => void } = {
installUpdate: () => {
getApi().installAppUpdate();
},
// Add other action functions here
};
export function useNotification() {
const notificationPopoverMode = useAtomValue(atoms.notificationPopoverMode);
const [notifications, setNotifications] = useAtom(atoms.notifications);
const [hoveredId, setHoveredId] = useState<string | null>(null);
const removeNotification = useCallback(
(id: string) => {
setNotifications((prevNotifications) => prevNotifications.filter((n) => n.id !== id));
},
[setNotifications]
);
const hideNotification = useCallback(
(id: string) => {
setNotifications((prevNotifications) =>
prevNotifications.map((n) => (n.id === id ? { ...n, hidden: true } : n))
);
},
[setNotifications]
);
const hideAllNotifications = useCallback(() => {
setNotifications((prevNotifications) => prevNotifications.map((n) => ({ ...n, hidden: true })));
}, [setNotifications]);
const removeAllNotifications = useCallback(() => {
setNotifications((prevNotifications) => prevNotifications.filter((n) => n.persistent));
}, [setNotifications]);
const copyNotification = useCallback(
(id: string) => {
const notif = notifications.find((n) => n.id === id);
if (!notif) return;
let text = notif.title ?? "";
if (notif.message) {
text += text.length > 0 ? `\n${notif.message}` : notif.message;
}
navigator.clipboard
.writeText(text)
.then(() => {
console.info("Text copied to clipboard");
})
.catch((err) => {
console.error("Failed to copy text: ", err);
});
},
[notifications]
);
const handleActionClick = useCallback(
(e: React.MouseEvent, action: NotificationActionType, id: string) => {
e.stopPropagation();
const actionFn = notificationActions[action.actionKey];
if (actionFn) {
actionFn();
removeNotification(id);
} else {
console.warn(`No action found for key: ${action.actionKey}`);
}
},
[removeNotification]
);
useEffect(() => {
if (notificationPopoverMode) {
return;
}
const hasExpiringNotifications = notifications.some((notif) => notif.expiration);
if (!hasExpiringNotifications) {
return;
}
const intervalId = setInterval(() => {
const now = Date.now();
setNotifications((prevNotifications) =>
prevNotifications.filter(
(notif) => !notif.expiration || notif.expiration > now || notif.id === hoveredId
)
);
}, 1000);
return () => clearInterval(intervalId);
}, [notificationPopoverMode, notifications, hoveredId, setNotifications]);
const formatTimestamp = (timestamp: string): string => {
const notificationTime = new Date(timestamp).getTime();
const now = Date.now();
const diffInSeconds = Math.floor((now - notificationTime) / 1000);
const diffInMinutes = Math.floor(diffInSeconds / 60);
const diffInHours = Math.floor(diffInMinutes / 60);
const diffInDays = Math.floor(diffInHours / 24);
if (diffInMinutes === 0) {
return `Just now`;
} else if (diffInMinutes < 60) {
return `${diffInMinutes} mins ago`;
} else if (diffInHours < 24) {
return `${diffInHours} hrs ago`;
} else if (diffInDays < 7) {
return `${diffInDays} days ago`;
} else {
return new Date(timestamp).toLocaleString();
}
};
return {
notifications,
hoveredId,
setHoveredId,
removeNotification,
removeAllNotifications,
hideNotification,
hideAllNotifications,
copyNotification,
handleActionClick,
formatTimestamp,
};
}