From 6960a439c9c2af261517b00b0b16a7fc5756c48b Mon Sep 17 00:00:00 2001 From: V Date: Sat, 1 Apr 2023 02:47:49 +0200 Subject: Add Notification log (#745) --- src/api/Notifications/notificationLog.tsx | 203 ++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 src/api/Notifications/notificationLog.tsx (limited to 'src/api/Notifications/notificationLog.tsx') diff --git a/src/api/Notifications/notificationLog.tsx b/src/api/Notifications/notificationLog.tsx new file mode 100644 index 0000000..72f09ac --- /dev/null +++ b/src/api/Notifications/notificationLog.tsx @@ -0,0 +1,203 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import * as DataStore from "@api/DataStore"; +import { Settings } from "@api/settings"; +import { classNameFactory } from "@api/Styles"; +import { useAwaiter } from "@utils/misc"; +import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { Alerts, Button, Forms, moment, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; +import { nanoid } from "nanoid"; +import type { DispatchWithoutAction } from "react"; + +import NotificationComponent from "./NotificationComponent"; +import type { NotificationData } from "./Notifications"; + +interface PersistentNotificationData extends Pick { + timestamp: number; + id: string; +} + +const KEY = "notification-log"; + +const getLog = async () => { + const log = await DataStore.get(KEY) as PersistentNotificationData[] | undefined; + return log ?? []; +}; + +const cl = classNameFactory("vc-notification-log-"); +const signals = new Set(); + +export async function persistNotification(notification: NotificationData) { + if (notification.noPersist) return; + + const limit = Settings.notifications.logLimit; + if (limit === 0) return; + + await DataStore.update(KEY, (old: PersistentNotificationData[] | undefined) => { + const log = old ?? []; + + // Omit stuff we don't need + const { + onClick, onClose, richBody, permanent, noPersist, dismissOnClick, + ...pureNotification + } = notification; + + log.unshift({ + ...pureNotification, + timestamp: Date.now(), + id: nanoid() + }); + + if (log.length > limit && limit !== 200) + log.length = limit; + + return log; + }); + + signals.forEach(x => x()); +} + +export async function deleteNotification(timestamp: number) { + const log = await getLog(); + const index = log.findIndex(x => x.timestamp === timestamp); + if (index === -1) return; + + log.splice(index, 1); + await DataStore.set(KEY, log); + signals.forEach(x => x()); +} + +export function useLogs() { + const [signal, setSignal] = useReducer(x => x + 1, 0); + + useEffect(() => { + signals.add(setSignal); + return () => void signals.delete(setSignal); + }, []); + + const [log, _, pending] = useAwaiter(getLog, { + fallbackValue: [], + deps: [signal] + }); + + return [log, pending] as const; +} + +function NotificationEntry({ data }: { data: PersistentNotificationData; }) { + const [removing, setRemoving] = useState(false); + const ref = React.useRef(null); + + useEffect(() => { + const div = ref.current!; + + const setHeight = () => { + if (div.clientHeight === 0) return requestAnimationFrame(setHeight); + div.style.height = `${div.clientHeight}px`; + }; + + setHeight(); + }, []); + + return ( +
+ { + if (removing) return; + setRemoving(true); + + setTimeout(() => deleteNotification(data.timestamp), 200); + }} + richBody={ +
+ {data.body} + +
+ } + /> +
+ ); +} + +export function NotificationLog({ log, pending }: { log: PersistentNotificationData[], pending: boolean; }) { + if (!log.length && !pending) + return ( +
+
+ + No notifications yet + +
+ ); + + return ( +
+ {log.map(n => )} +
+ ); +} + +function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void; }) { + const [log, pending] = useLogs(); + + return ( + + + Notification Log + + + + + + + + + + + + ); +} + +export function openNotificationLogModal() { + const key = openModal(modalProps => ( + closeModal(key)} + /> + )); +} -- cgit