diff options
author | V <vendicated@riseup.net> | 2023-04-01 02:47:49 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-01 02:47:49 +0200 |
commit | 6960a439c9c2af261517b00b0b16a7fc5756c48b (patch) | |
tree | 4e1832c5cc295b26046e193ac5cf60bcd2590568 /src/api/Notifications/notificationLog.tsx | |
parent | 4dff1c5bd5b16e926bc628acf11118344832a374 (diff) | |
download | Vencord-6960a439c9c2af261517b00b0b16a7fc5756c48b.tar.gz Vencord-6960a439c9c2af261517b00b0b16a7fc5756c48b.tar.bz2 Vencord-6960a439c9c2af261517b00b0b16a7fc5756c48b.zip |
Add Notification log (#745)
Diffstat (limited to 'src/api/Notifications/notificationLog.tsx')
-rw-r--r-- | src/api/Notifications/notificationLog.tsx | 203 |
1 files changed, 203 insertions, 0 deletions
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 <https://www.gnu.org/licenses/>. +*/ + +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<NotificationData, "title" | "body" | "image" | "icon" | "color"> { + 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<DispatchWithoutAction>(); + +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<HTMLDivElement>(null); + + useEffect(() => { + const div = ref.current!; + + const setHeight = () => { + if (div.clientHeight === 0) return requestAnimationFrame(setHeight); + div.style.height = `${div.clientHeight}px`; + }; + + setHeight(); + }, []); + + return ( + <div className={cl("wrapper", { removing })} ref={ref}> + <NotificationComponent + {...data} + permanent={true} + dismissOnClick={false} + onClose={() => { + if (removing) return; + setRemoving(true); + + setTimeout(() => deleteNotification(data.timestamp), 200); + }} + richBody={ + <div className={cl("body")}> + {data.body} + <Timestamp timestamp={moment(data.timestamp)} className={cl("timestamp")} /> + </div> + } + /> + </div> + ); +} + +export function NotificationLog({ log, pending }: { log: PersistentNotificationData[], pending: boolean; }) { + if (!log.length && !pending) + return ( + <div className={cl("container")}> + <div className={cl("empty")} /> + <Forms.FormText style={{ textAlign: "center" }}> + No notifications yet + </Forms.FormText> + </div> + ); + + return ( + <div className={cl("container")}> + {log.map(n => <NotificationEntry data={n} key={n.id} />)} + </div> + ); +} + +function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void; }) { + const [log, pending] = useLogs(); + + return ( + <ModalRoot {...modalProps} size={ModalSize.LARGE}> + <ModalHeader> + <Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Notification Log</Text> + <ModalCloseButton onClick={close} /> + </ModalHeader> + + <ModalContent> + <NotificationLog log={log} pending={pending} /> + </ModalContent> + + <ModalFooter> + <Button + disabled={log.length === 0} + onClick={() => { + Alerts.show({ + title: "Are you sure?", + body: `This will permanently remove ${log.length} notification${log.length === 1 ? "" : "s"}. This action cannot be undone.`, + async onConfirm() { + await DataStore.set(KEY, []); + signals.forEach(x => x()); + }, + confirmText: "Do it!", + confirmColor: "vc-notification-log-danger-btn", + cancelText: "Nevermind" + }); + }} + > + Clear Notification Log + </Button> + </ModalFooter> + </ModalRoot> + ); +} + +export function openNotificationLogModal() { + const key = openModal(modalProps => ( + <LogModal + modalProps={modalProps} + close={() => closeModal(key)} + /> + )); +} |