mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-27 15:34:26 -04:00
parent
163c35ff87
commit
b56e03fd2f
34 changed files with 1076 additions and 3010 deletions
|
@ -1,26 +1,37 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
* 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 { classNameFactory } from "@api/Styles";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { InfoIcon } from "@components/Icons";
|
||||
import { openUserProfile } from "@utils/discord";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { closeAllModals, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import { LazyComponent, useAwaiter } from "@utils/react";
|
||||
import { find, findByCode, findByCodeLazy } from "@webpack";
|
||||
import { Alerts, Button, ChannelStore, ContextMenuApi, FluxDispatcher, Menu, NavigationRouter, React, TabBar, Text, TextInput, Tooltip, useMemo, useRef, useState } from "@webpack/common";
|
||||
import { Alerts, Button, ChannelStore, ContextMenuApi, FluxDispatcher, Menu, NavigationRouter, React, TabBar, Text, TextInput, useCallback, useMemo, useRef, useState } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
|
||||
import { clearMessagesIDB, DBMessageRecord, deleteMessageIDB, deleteMessagesBulkIDB } from "../db";
|
||||
import { settings } from "../index";
|
||||
import { LoggedMessage, LoggedMessageJSON } from "../types";
|
||||
import { messageJsonToMessageClass } from "../utils";
|
||||
import { importLogs } from "../utils/settingsUtils";
|
||||
import { useMessages } from "./hooks";
|
||||
import { clearLogs, defaultLoggedMessages, removeLog, removeLogs, savedLoggedMessages } from "../LoggedMessageManager";
|
||||
import { LoggedMessage, LoggedMessageJSON, LoggedMessages } from "../types";
|
||||
import { isGhostPinged, messageJsonToMessageClass, sortMessagesByDate } from "../utils";
|
||||
import { doesMatch, parseQuery } from "../utils/parseQuery";
|
||||
|
||||
|
||||
|
||||
export interface MessagePreviewProps {
|
||||
className: string;
|
||||
|
@ -56,7 +67,7 @@ const ChildrenAccessories = LazyComponent<ChildrenAccProops>(() => findByCode("c
|
|||
|
||||
const cl = classNameFactory("msg-logger-modal-");
|
||||
|
||||
export enum LogTabs {
|
||||
enum LogTabs {
|
||||
DELETED = "Deleted",
|
||||
EDITED = "Edited",
|
||||
GHOST_PING = "Ghost Pinged"
|
||||
|
@ -68,14 +79,73 @@ interface Props {
|
|||
}
|
||||
|
||||
export function LogsModal({ modalProps, initalQuery }: Props) {
|
||||
const [x, setX] = useState(0);
|
||||
const forceUpdate = () => setX(e => e + 1);
|
||||
|
||||
const [logs, _, pending] = useAwaiter(async () => savedLoggedMessages, {
|
||||
fallbackValue: defaultLoggedMessages as LoggedMessages,
|
||||
deps: [x]
|
||||
});
|
||||
const [currentTab, setCurrentTab] = useState(LogTabs.DELETED);
|
||||
const [queryEh, setQuery] = useState(initalQuery ?? "");
|
||||
const [sortNewest, setSortNewest] = useState(settings.store.sortNewest);
|
||||
const [numDisplayedMessages, setNumDisplayedMessages] = useState(settings.store.messagesToDisplayAtOnceInLogs);
|
||||
const [numDisplayedMessages, setNumDisplayedMessages] = useState(50);
|
||||
const contentRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const { messages, total, statusTotal, pending, reset } = useMessages(queryEh, currentTab, sortNewest, numDisplayedMessages);
|
||||
const handleLoadMore = useCallback(() => {
|
||||
setNumDisplayedMessages(prevNum => prevNum + 50);
|
||||
}, []);
|
||||
|
||||
|
||||
// Flogger.log(logs, _, pending, contentRef);
|
||||
|
||||
// Flogger.time("hi");
|
||||
const messages: string[][] = currentTab === LogTabs.DELETED || currentTab === LogTabs.GHOST_PING
|
||||
? Object.values(logs?.deletedMessages ?? {})
|
||||
: Object.values(logs?.editedMessages ?? {});
|
||||
|
||||
const flattendAndfilteredAndSortedMessages = useMemo(() => {
|
||||
const { success, type, id, negate, query } = parseQuery(queryEh);
|
||||
|
||||
if (query === "" && !success) {
|
||||
const result = messages
|
||||
.flat()
|
||||
.filter(m => currentTab === LogTabs.GHOST_PING ? isGhostPinged(logs[m].message!) : true)
|
||||
.sort(sortMessagesByDate);
|
||||
return sortNewest ? result : result.reverse();
|
||||
}
|
||||
|
||||
const result = messages
|
||||
.flat()
|
||||
.filter(m =>
|
||||
currentTab === LogTabs.GHOST_PING
|
||||
? isGhostPinged(logs[m].message)
|
||||
: true
|
||||
)
|
||||
.filter(m =>
|
||||
logs[m]?.message != null &&
|
||||
(
|
||||
success === false
|
||||
? true
|
||||
: negate
|
||||
? !doesMatch(type!, id!, logs[m].message!)
|
||||
: doesMatch(type!, id!, logs[m].message!)
|
||||
)
|
||||
)
|
||||
.filter(m =>
|
||||
logs[m]?.message?.content?.toLowerCase()?.includes(query.toLowerCase()) ??
|
||||
logs[m].message?.editHistory?.map(m => m.content?.toLowerCase()).includes(query.toLowerCase())
|
||||
)
|
||||
.sort(sortMessagesByDate);
|
||||
|
||||
return sortNewest ? result : result.reverse();
|
||||
}, [currentTab, logs, queryEh, sortNewest]);
|
||||
|
||||
const visibleMessages = flattendAndfilteredAndSortedMessages.slice(0, numDisplayedMessages);
|
||||
|
||||
const canLoadMore = numDisplayedMessages < flattendAndfilteredAndSortedMessages.length;
|
||||
|
||||
// Flogger.timeEnd("hi");
|
||||
return (
|
||||
<ModalRoot className={cl("root")} {...modalProps} size={ModalSize.LARGE}>
|
||||
<ModalHeader className={cl("header")}>
|
||||
|
@ -87,7 +157,7 @@ export function LogsModal({ modalProps, initalQuery }: Props) {
|
|||
selectedItem={currentTab}
|
||||
onItemSelect={e => {
|
||||
setCurrentTab(e);
|
||||
setNumDisplayedMessages(settings.store.messagesToDisplayAtOnceInLogs);
|
||||
setNumDisplayedMessages(50);
|
||||
contentRef.current?.firstElementChild?.scrollTo(0, 0);
|
||||
// forceUpdate();
|
||||
}}
|
||||
|
@ -118,23 +188,21 @@ export function LogsModal({ modalProps, initalQuery }: Props) {
|
|||
<ModalContent
|
||||
className={cl("content")}
|
||||
>
|
||||
{messages != null && total === 0 && (
|
||||
<EmptyLogs
|
||||
hasQuery={queryEh.length !== 0}
|
||||
reset={reset}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!pending && messages != null && (
|
||||
<LogsContentMemo
|
||||
visibleMessages={messages}
|
||||
canLoadMore={messages.length < statusTotal && messages.length >= settings.store.messagesToDisplayAtOnceInLogs}
|
||||
tab={currentTab}
|
||||
sortNewest={sortNewest}
|
||||
reset={reset}
|
||||
handleLoadMore={() => setNumDisplayedMessages(e => e + settings.store.messagesToDisplayAtOnceInLogs)}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
pending || logs == null || messages.length === 0
|
||||
? <EmptyLogs />
|
||||
: (
|
||||
<LogsContentMemo
|
||||
visibleMessages={visibleMessages}
|
||||
canLoadMore={canLoadMore}
|
||||
tab={currentTab}
|
||||
logs={logs}
|
||||
sortNewest={sortNewest}
|
||||
forceUpdate={forceUpdate}
|
||||
handleLoadMore={handleLoadMore}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</ModalContent>
|
||||
}
|
||||
</div>
|
||||
|
@ -148,8 +216,8 @@ export function LogsModal({ modalProps, initalQuery }: Props) {
|
|||
confirmColor: Button.Colors.RED,
|
||||
cancelText: "Cancel",
|
||||
onConfirm: async () => {
|
||||
await clearMessagesIDB();
|
||||
reset();
|
||||
await clearLogs();
|
||||
forceUpdate();
|
||||
}
|
||||
|
||||
})}
|
||||
|
@ -159,16 +227,16 @@ export function LogsModal({ modalProps, initalQuery }: Props) {
|
|||
<Button
|
||||
style={{ marginRight: "16px" }}
|
||||
color={Button.Colors.YELLOW}
|
||||
disabled={messages?.length === 0}
|
||||
disabled={visibleMessages.length === 0}
|
||||
onClick={() => Alerts.show({
|
||||
title: "Clear Logs",
|
||||
body: `Are you sure you want to clear ${messages.length} logs`,
|
||||
body: `Are you sure you want to clear ${visibleMessages.length} logs`,
|
||||
confirmText: "Clear",
|
||||
confirmColor: Button.Colors.RED,
|
||||
cancelText: "Cancel",
|
||||
onConfirm: async () => {
|
||||
await deleteMessagesBulkIDB(messages.map(e => e.message_id));
|
||||
reset();
|
||||
await removeLogs(visibleMessages);
|
||||
forceUpdate();
|
||||
}
|
||||
})}
|
||||
>
|
||||
|
@ -194,27 +262,28 @@ export function LogsModal({ modalProps, initalQuery }: Props) {
|
|||
}
|
||||
|
||||
interface LogContentProps {
|
||||
logs: LoggedMessages,
|
||||
sortNewest: boolean;
|
||||
tab: LogTabs;
|
||||
visibleMessages: DBMessageRecord[];
|
||||
visibleMessages: string[];
|
||||
canLoadMore: boolean;
|
||||
reset: () => void;
|
||||
forceUpdate: () => void;
|
||||
handleLoadMore: () => void;
|
||||
}
|
||||
|
||||
function LogsContent({ visibleMessages, canLoadMore, sortNewest, tab, reset, handleLoadMore }: LogContentProps) {
|
||||
function LogsContent({ logs, visibleMessages, canLoadMore, sortNewest, tab, forceUpdate, handleLoadMore }: LogContentProps) {
|
||||
if (visibleMessages.length === 0)
|
||||
return <NoResults tab={tab} />;
|
||||
|
||||
return (
|
||||
<div className={cl("content-inner")}>
|
||||
{visibleMessages
|
||||
.map(({ message }, i) => (
|
||||
.map((id, i) => (
|
||||
<LMessage
|
||||
key={message.id}
|
||||
log={{ message }}
|
||||
reset={reset}
|
||||
isGroupStart={isGroupStart(message, visibleMessages[i - 1]?.message, sortNewest)}
|
||||
key={id}
|
||||
log={logs[id] as { message: LoggedMessageJSON; }}
|
||||
forceUpdate={forceUpdate}
|
||||
isGroupStart={isGroupStart(logs[id]?.message, logs[visibleMessages[i - 1]]?.message, sortNewest)}
|
||||
/>
|
||||
))}
|
||||
{
|
||||
|
@ -261,35 +330,12 @@ function NoResults({ tab }: { tab: LogTabs; }) {
|
|||
);
|
||||
}
|
||||
|
||||
function EmptyLogs({ hasQuery, reset: forceUpdate }: { hasQuery: boolean; reset: () => void; }) {
|
||||
function EmptyLogs() {
|
||||
return (
|
||||
<div className={cl("empty-logs", "content-inner")} style={{ textAlign: "center" }}>
|
||||
<Flex flexDirection="column" style={{ position: "relative" }}>
|
||||
|
||||
<Text variant="text-lg/normal">
|
||||
Empty eh
|
||||
</Text>
|
||||
|
||||
{!hasQuery && (
|
||||
<>
|
||||
<Tooltip text="ML Enhanced now stores logs in indexeddb. You need to import your old logs from the logs directory. Importing wont overwrite existing logs">
|
||||
{({ onMouseEnter, onMouseLeave }) => (
|
||||
<div
|
||||
className={cl("info-icon")}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<InfoIcon />
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
||||
<Button onClick={() => importLogs().then(() => forceUpdate())}>
|
||||
Import Logs
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
<Text variant="text-lg/normal">
|
||||
Empty eh
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -298,13 +344,11 @@ function EmptyLogs({ hasQuery, reset: forceUpdate }: { hasQuery: boolean; reset:
|
|||
interface LMessageProps {
|
||||
log: { message: LoggedMessageJSON; };
|
||||
isGroupStart: boolean,
|
||||
reset: () => void;
|
||||
forceUpdate: () => void;
|
||||
}
|
||||
function LMessage({ log, isGroupStart, reset, }: LMessageProps) {
|
||||
function LMessage({ log, isGroupStart, forceUpdate, }: LMessageProps) {
|
||||
const message = useMemo(() => messageJsonToMessageClass(log), [log]);
|
||||
|
||||
// console.log(message);
|
||||
|
||||
if (!message) return null;
|
||||
|
||||
return (
|
||||
|
@ -326,6 +370,7 @@ function LMessage({ log, isGroupStart, reset, }: LMessageProps) {
|
|||
closeAllModals();
|
||||
}}
|
||||
/>
|
||||
|
||||
<Menu.MenuItem
|
||||
key="open-user-profile"
|
||||
id="open-user-profile"
|
||||
|
@ -382,7 +427,10 @@ function LMessage({ log, isGroupStart, reset, }: LMessageProps) {
|
|||
label="Delete Log"
|
||||
color="danger"
|
||||
action={() =>
|
||||
deleteMessageIDB(log.message.id).then(() => reset())
|
||||
removeLog(log.message.id)
|
||||
.then(() => {
|
||||
forceUpdate();
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
|
@ -430,8 +478,6 @@ function isGroupStart(
|
|||
) {
|
||||
if (!currentMessage || !previousMessage) return true;
|
||||
|
||||
if (currentMessage.id === previousMessage.id) return true;
|
||||
|
||||
const [newestMessage, oldestMessage] = sortNewest
|
||||
? [previousMessage, currentMessage]
|
||||
: [currentMessage, previousMessage];
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "@webpack/common";
|
||||
|
||||
import { countMessagesByStatusIDB, countMessagesIDB, DBMessageRecord, DBMessageStatus, getDateStortedMessagesByStatusIDB } from "../db";
|
||||
import { doesMatch, tokenizeQuery } from "../utils/parseQuery";
|
||||
import { LogTabs } from "./LogsModal";
|
||||
|
||||
function useDebouncedValue<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
|
||||
// this is so shit
|
||||
export function useMessages(query: string, currentTab: LogTabs, sortNewest: boolean, numDisplayedMessages: number) {
|
||||
// only for initial load
|
||||
const [pending, setPending] = useState(true);
|
||||
const [messages, setMessages] = useState<DBMessageRecord[]>([]);
|
||||
const [statusTotal, setStatusTotal] = useState<number>(0);
|
||||
const [total, setTotal] = useState<number>(0);
|
||||
|
||||
const debouncedQuery = useDebouncedValue(query, 300);
|
||||
|
||||
useEffect(() => {
|
||||
countMessagesIDB().then(x => setTotal(x));
|
||||
}, [pending]);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
const loadMessages = async () => {
|
||||
const status = getStatus(currentTab);
|
||||
|
||||
if (debouncedQuery === "") {
|
||||
const [messages, statusTotal] = await Promise.all([
|
||||
getDateStortedMessagesByStatusIDB(sortNewest, numDisplayedMessages, status),
|
||||
countMessagesByStatusIDB(status),
|
||||
]);
|
||||
|
||||
|
||||
if (isMounted) {
|
||||
setMessages(messages);
|
||||
setStatusTotal(statusTotal);
|
||||
}
|
||||
|
||||
setPending(false);
|
||||
} else {
|
||||
const allMessages = await getDateStortedMessagesByStatusIDB(sortNewest, Number.MAX_SAFE_INTEGER, status);
|
||||
const { queries, rest } = tokenizeQuery(debouncedQuery);
|
||||
|
||||
const filteredMessages = allMessages.filter(record => {
|
||||
for (const query of queries) {
|
||||
const matching = doesMatch(query.key, query.value, record.message);
|
||||
if (query.negate ? matching : !matching) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return rest.every(r =>
|
||||
record.message.content.toLowerCase().includes(r.toLowerCase())
|
||||
);
|
||||
});
|
||||
|
||||
if (isMounted) {
|
||||
setMessages(filteredMessages);
|
||||
setStatusTotal(Number.MAX_SAFE_INTEGER);
|
||||
}
|
||||
setPending(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadMessages();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
|
||||
}, [debouncedQuery, sortNewest, numDisplayedMessages, currentTab, pending]);
|
||||
|
||||
|
||||
return { messages, statusTotal, total, pending, reset: () => setPending(true) };
|
||||
}
|
||||
|
||||
|
||||
function getStatus(currentTab: LogTabs) {
|
||||
switch (currentTab) {
|
||||
case LogTabs.DELETED:
|
||||
return DBMessageStatus.DELETED;
|
||||
case LogTabs.EDITED:
|
||||
return DBMessageStatus.EDITED;
|
||||
default:
|
||||
return DBMessageStatus.GHOST_PINGED;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue