/* * 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)} /> )); }