mirror of
https://github.com/Vendicated/Vencord.git
synced 2024-09-19 22:20:34 +00:00
lint fix
This commit is contained in:
parent
b0daabdf71
commit
e41fed6372
6 changed files with 751 additions and 752 deletions
|
@ -1,25 +1,25 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import { findExportedComponentLazy } from "@webpack";
|
||||
import { React } from "@webpack/common";
|
||||
|
||||
import { NotesDataIcon } from "./Icons";
|
||||
import { openNotesDataModal } from "./NotesDataModal";
|
||||
|
||||
const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider");
|
||||
|
||||
export const OpenNotesDataButton = LazyComponent(() => React.memo(() => {
|
||||
return (
|
||||
<HeaderBarIcon
|
||||
className="vc-notes-searcher-toolbox-button"
|
||||
onClick={() => openNotesDataModal()}
|
||||
tooltip={"View Notes"}
|
||||
icon={NotesDataIcon}
|
||||
/>
|
||||
);
|
||||
}));
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import { findExportedComponentLazy } from "@webpack";
|
||||
import { React } from "@webpack/common";
|
||||
|
||||
import { NotesDataIcon } from "./Icons";
|
||||
import { openNotesDataModal } from "./NotesDataModal";
|
||||
|
||||
const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider");
|
||||
|
||||
export const OpenNotesDataButton = LazyComponent(() => React.memo(() => {
|
||||
return (
|
||||
<HeaderBarIcon
|
||||
className="vc-notes-searcher-toolbox-button"
|
||||
onClick={() => openNotesDataModal()}
|
||||
tooltip={"View Notes"}
|
||||
icon={NotesDataIcon}
|
||||
/>
|
||||
);
|
||||
}));
|
||||
|
|
|
@ -1,217 +1,217 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import {
|
||||
closeModal, ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, openModal
|
||||
} from "@utils/modal";
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import { Button, React, RelationshipStore, Select, Text, TextInput, useCallback, useMemo, useReducer, useState } from "@webpack/common";
|
||||
|
||||
import { cacheUsers, getNotes, usersCache as usersCache$1 } from "../data";
|
||||
import NotesDataRow from "./NotesDataRow";
|
||||
|
||||
const cl = classNameFactory("vc-notes-searcher-modal-");
|
||||
|
||||
const enum SearchStatus {
|
||||
ALL,
|
||||
FRIENDS,
|
||||
BLOCKED,
|
||||
}
|
||||
|
||||
const filterUser = (query: string, userId: string, userNotes: string) => {
|
||||
if (query === "" || userId.includes(query)) return true;
|
||||
|
||||
query = query.toLowerCase();
|
||||
|
||||
const user = usersCache$1.get(userId);
|
||||
|
||||
return user && (
|
||||
user.globalName?.toLowerCase().includes(query) || user.username.toLowerCase().includes(query)
|
||||
) || userNotes.toLowerCase().includes(query);
|
||||
};
|
||||
|
||||
// looks like a shit but I don't know better way to do it
|
||||
// P.S. using `getNotes()` as deps for useMemo won't work due to object init outside of component
|
||||
let RefreshNotesDataEx: () => void | undefined;
|
||||
|
||||
export const refreshNotesData = () => {
|
||||
if (!RefreshNotesDataEx) return;
|
||||
|
||||
RefreshNotesDataEx();
|
||||
};
|
||||
|
||||
export function NotesDataModal({ modalProps, close }: {
|
||||
modalProps: ModalProps;
|
||||
close(): void;
|
||||
}) {
|
||||
const [searchValue, setSearchValue] = useState({ query: "", status: SearchStatus.ALL });
|
||||
|
||||
const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, query }));
|
||||
const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));
|
||||
|
||||
const [usersNotesData, refreshNotesData] = useReducer(() => {
|
||||
return Object.entries(getNotes())
|
||||
.map<[string, string]>(([userId, { note }]) => [userId, note])
|
||||
.filter((([_, note]) => note !== ""));
|
||||
},
|
||||
Object.entries(getNotes())
|
||||
.map<[string, string]>(([userId, { note }]) => [userId, note])
|
||||
.filter((([_, note]) => note !== ""))
|
||||
);
|
||||
|
||||
RefreshNotesDataEx = refreshNotesData;
|
||||
|
||||
const filteredNotes = useMemo(() => {
|
||||
const { query, status } = searchValue;
|
||||
|
||||
if (query === "" && status === SearchStatus.ALL) {
|
||||
return usersNotesData;
|
||||
}
|
||||
|
||||
return usersNotesData
|
||||
.filter(([userId, userNotes]) => {
|
||||
switch (status) {
|
||||
case SearchStatus.FRIENDS:
|
||||
return RelationshipStore.isFriend(userId) && filterUser(query, userId, userNotes);
|
||||
case SearchStatus.BLOCKED:
|
||||
return RelationshipStore.isBlocked(userId) && filterUser(query, userId, userNotes);
|
||||
default:
|
||||
return filterUser(query, userId, userNotes);
|
||||
}
|
||||
});
|
||||
}, [usersNotesData, searchValue]);
|
||||
|
||||
const [visibleNotesNum, setVisibleNotesNum] = useState(10);
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
setVisibleNotesNum(prevNum => prevNum + 10);
|
||||
}, []);
|
||||
|
||||
const visibleNotes = filteredNotes.slice(0, visibleNotesNum);
|
||||
|
||||
const canLoadMore = visibleNotesNum < filteredNotes.length;
|
||||
|
||||
return (
|
||||
<ModalRoot className={cl("root")} {...modalProps}>
|
||||
<ModalHeader className={cl("header")}>
|
||||
<Text className={cl("header-text")} variant="heading-lg/semibold" style={{ whiteSpace: "nowrap", width: "fit-content", marginRight: "16px" }}>User Notes</Text>
|
||||
<TextInput className={cl("header-input")} value={searchValue.query} onChange={onSearch} placeholder="Filter Notes (ID/Display Name/Username/Note Text)" style={{ width: "100% !important", marginRight: "16px" }} />
|
||||
<div className={cl("header-user-type")} style={{ minWidth: "160px", marginRight: "16px" }}>
|
||||
<Select
|
||||
options={[
|
||||
{ label: "Show All", value: SearchStatus.ALL, default: true },
|
||||
{ label: "Show Friends", value: SearchStatus.FRIENDS },
|
||||
{ label: "Show Blocked", value: SearchStatus.BLOCKED },
|
||||
]}
|
||||
serialize={String}
|
||||
select={onStatusChange}
|
||||
isSelected={v => v === searchValue.status}
|
||||
closeOnSelect={true}
|
||||
/>
|
||||
</div>
|
||||
<ModalCloseButton onClick={close} />
|
||||
</ModalHeader>
|
||||
<div style={{ opacity: modalProps.transitionState === 1 ? "1" : "0", overflow: "hidden", height: "100%" }} className={cl("content-container")}>
|
||||
{
|
||||
modalProps.transitionState === 1 &&
|
||||
<ModalContent className={cl("content")}>
|
||||
{
|
||||
!visibleNotes.length ? <NoNotes /> : (
|
||||
<NotesDataContent
|
||||
visibleNotes={visibleNotes}
|
||||
canLoadMore={canLoadMore}
|
||||
loadMore={loadMore}
|
||||
refreshNotesData={refreshNotesData}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</ModalContent>
|
||||
}
|
||||
</div>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
|
||||
// looks like a shit but I don't know better way to do it
|
||||
// P.S. using `usersCache` as deps for useMemo won't work due to object init outside of component
|
||||
let RefreshUsersCacheEx: () => void | undefined;
|
||||
|
||||
export const refreshUsersCache = () => {
|
||||
if (!RefreshUsersCacheEx) return;
|
||||
|
||||
RefreshUsersCacheEx();
|
||||
};
|
||||
|
||||
const NotesDataContent = ({ visibleNotes, canLoadMore, loadMore, refreshNotesData }: {
|
||||
visibleNotes: [string, string][];
|
||||
canLoadMore: boolean;
|
||||
loadMore(): void;
|
||||
refreshNotesData(): void;
|
||||
}) => {
|
||||
if (!visibleNotes.length)
|
||||
return <NoNotes />;
|
||||
|
||||
const [usersCache, refreshUsersCache] = useReducer(() => {
|
||||
return new Map(usersCache$1);
|
||||
}, usersCache$1);
|
||||
|
||||
RefreshUsersCacheEx = refreshUsersCache;
|
||||
|
||||
return (
|
||||
<div className={cl("content-inner")} style={{ paddingTop: "16px", height: "fit-content" }}>
|
||||
{
|
||||
visibleNotes
|
||||
.map(([userId, userNotes]) => {
|
||||
return (
|
||||
<NotesDataRow
|
||||
key={userId}
|
||||
userId={userId}
|
||||
userNotes={userNotes}
|
||||
usersCache={usersCache}
|
||||
refreshNotesData={refreshNotesData}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
{
|
||||
canLoadMore &&
|
||||
<Button
|
||||
className={cl("load-more")}
|
||||
size={Button.Sizes.NONE}
|
||||
style={{ marginTop: "16px", width: "100%", height: "32px" }}
|
||||
onClick={() => loadMore()}
|
||||
>
|
||||
Load More
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NoNotes = LazyComponent(() => React.memo(() => (
|
||||
<div className={cl("no-notes")} style={{ textAlign: "center", display: "grid", placeContent: "center", height: "100%" }}>
|
||||
<Text variant="text-lg/normal">
|
||||
No Notes.
|
||||
</Text>
|
||||
</div>
|
||||
)));
|
||||
|
||||
let fistTimeOpen = true;
|
||||
|
||||
export const openNotesDataModal = async () => {
|
||||
if (fistTimeOpen) {
|
||||
cacheUsers();
|
||||
fistTimeOpen = false;
|
||||
}
|
||||
|
||||
const key = openModal(modalProps => (
|
||||
<NotesDataModal
|
||||
modalProps={modalProps}
|
||||
close={() => closeModal(key)}
|
||||
/>
|
||||
));
|
||||
};
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import {
|
||||
closeModal, ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, openModal
|
||||
} from "@utils/modal";
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import { Button, React, RelationshipStore, Select, Text, TextInput, useCallback, useMemo, useReducer, useState } from "@webpack/common";
|
||||
|
||||
import { cacheUsers, getNotes, usersCache as usersCache$1 } from "../data";
|
||||
import NotesDataRow from "./NotesDataRow";
|
||||
|
||||
const cl = classNameFactory("vc-notes-searcher-modal-");
|
||||
|
||||
const enum SearchStatus {
|
||||
ALL,
|
||||
FRIENDS,
|
||||
BLOCKED,
|
||||
}
|
||||
|
||||
const filterUser = (query: string, userId: string, userNotes: string) => {
|
||||
if (query === "" || userId.includes(query)) return true;
|
||||
|
||||
query = query.toLowerCase();
|
||||
|
||||
const user = usersCache$1.get(userId);
|
||||
|
||||
return user && (
|
||||
user.globalName?.toLowerCase().includes(query) || user.username.toLowerCase().includes(query)
|
||||
) || userNotes.toLowerCase().includes(query);
|
||||
};
|
||||
|
||||
// looks like a shit but I don't know better way to do it
|
||||
// P.S. using `getNotes()` as deps for useMemo won't work due to object init outside of component
|
||||
let RefreshNotesDataEx: () => void | undefined;
|
||||
|
||||
export const refreshNotesData = () => {
|
||||
if (!RefreshNotesDataEx) return;
|
||||
|
||||
RefreshNotesDataEx();
|
||||
};
|
||||
|
||||
export function NotesDataModal({ modalProps, close }: {
|
||||
modalProps: ModalProps;
|
||||
close(): void;
|
||||
}) {
|
||||
const [searchValue, setSearchValue] = useState({ query: "", status: SearchStatus.ALL });
|
||||
|
||||
const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, query }));
|
||||
const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));
|
||||
|
||||
const [usersNotesData, refreshNotesData] = useReducer(() => {
|
||||
return Object.entries(getNotes())
|
||||
.map<[string, string]>(([userId, { note }]) => [userId, note])
|
||||
.filter((([_, note]) => note !== ""));
|
||||
},
|
||||
Object.entries(getNotes())
|
||||
.map<[string, string]>(([userId, { note }]) => [userId, note])
|
||||
.filter((([_, note]) => note !== ""))
|
||||
);
|
||||
|
||||
RefreshNotesDataEx = refreshNotesData;
|
||||
|
||||
const filteredNotes = useMemo(() => {
|
||||
const { query, status } = searchValue;
|
||||
|
||||
if (query === "" && status === SearchStatus.ALL) {
|
||||
return usersNotesData;
|
||||
}
|
||||
|
||||
return usersNotesData
|
||||
.filter(([userId, userNotes]) => {
|
||||
switch (status) {
|
||||
case SearchStatus.FRIENDS:
|
||||
return RelationshipStore.isFriend(userId) && filterUser(query, userId, userNotes);
|
||||
case SearchStatus.BLOCKED:
|
||||
return RelationshipStore.isBlocked(userId) && filterUser(query, userId, userNotes);
|
||||
default:
|
||||
return filterUser(query, userId, userNotes);
|
||||
}
|
||||
});
|
||||
}, [usersNotesData, searchValue]);
|
||||
|
||||
const [visibleNotesNum, setVisibleNotesNum] = useState(10);
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
setVisibleNotesNum(prevNum => prevNum + 10);
|
||||
}, []);
|
||||
|
||||
const visibleNotes = filteredNotes.slice(0, visibleNotesNum);
|
||||
|
||||
const canLoadMore = visibleNotesNum < filteredNotes.length;
|
||||
|
||||
return (
|
||||
<ModalRoot className={cl("root")} {...modalProps}>
|
||||
<ModalHeader className={cl("header")}>
|
||||
<Text className={cl("header-text")} variant="heading-lg/semibold" style={{ whiteSpace: "nowrap", width: "fit-content", marginRight: "16px" }}>User Notes</Text>
|
||||
<TextInput className={cl("header-input")} value={searchValue.query} onChange={onSearch} placeholder="Filter Notes (ID/Display Name/Username/Note Text)" style={{ width: "100% !important", marginRight: "16px" }} />
|
||||
<div className={cl("header-user-type")} style={{ minWidth: "160px", marginRight: "16px" }}>
|
||||
<Select
|
||||
options={[
|
||||
{ label: "Show All", value: SearchStatus.ALL, default: true },
|
||||
{ label: "Show Friends", value: SearchStatus.FRIENDS },
|
||||
{ label: "Show Blocked", value: SearchStatus.BLOCKED },
|
||||
]}
|
||||
serialize={String}
|
||||
select={onStatusChange}
|
||||
isSelected={v => v === searchValue.status}
|
||||
closeOnSelect={true}
|
||||
/>
|
||||
</div>
|
||||
<ModalCloseButton onClick={close} />
|
||||
</ModalHeader>
|
||||
<div style={{ opacity: modalProps.transitionState === 1 ? "1" : "0", overflow: "hidden", height: "100%" }} className={cl("content-container")}>
|
||||
{
|
||||
modalProps.transitionState === 1 &&
|
||||
<ModalContent className={cl("content")}>
|
||||
{
|
||||
!visibleNotes.length ? <NoNotes /> : (
|
||||
<NotesDataContent
|
||||
visibleNotes={visibleNotes}
|
||||
canLoadMore={canLoadMore}
|
||||
loadMore={loadMore}
|
||||
refreshNotesData={refreshNotesData}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</ModalContent>
|
||||
}
|
||||
</div>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
|
||||
// looks like a shit but I don't know better way to do it
|
||||
// P.S. using `usersCache` as deps for useMemo won't work due to object init outside of component
|
||||
let RefreshUsersCacheEx: () => void | undefined;
|
||||
|
||||
export const refreshUsersCache = () => {
|
||||
if (!RefreshUsersCacheEx) return;
|
||||
|
||||
RefreshUsersCacheEx();
|
||||
};
|
||||
|
||||
const NotesDataContent = ({ visibleNotes, canLoadMore, loadMore, refreshNotesData }: {
|
||||
visibleNotes: [string, string][];
|
||||
canLoadMore: boolean;
|
||||
loadMore(): void;
|
||||
refreshNotesData(): void;
|
||||
}) => {
|
||||
if (!visibleNotes.length)
|
||||
return <NoNotes />;
|
||||
|
||||
const [usersCache, refreshUsersCache] = useReducer(() => {
|
||||
return new Map(usersCache$1);
|
||||
}, usersCache$1);
|
||||
|
||||
RefreshUsersCacheEx = refreshUsersCache;
|
||||
|
||||
return (
|
||||
<div className={cl("content-inner")} style={{ paddingTop: "16px", height: "fit-content" }}>
|
||||
{
|
||||
visibleNotes
|
||||
.map(([userId, userNotes]) => {
|
||||
return (
|
||||
<NotesDataRow
|
||||
key={userId}
|
||||
userId={userId}
|
||||
userNotes={userNotes}
|
||||
usersCache={usersCache}
|
||||
refreshNotesData={refreshNotesData}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
{
|
||||
canLoadMore &&
|
||||
<Button
|
||||
className={cl("load-more")}
|
||||
size={Button.Sizes.NONE}
|
||||
style={{ marginTop: "16px", width: "100%", height: "32px" }}
|
||||
onClick={() => loadMore()}
|
||||
>
|
||||
Load More
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NoNotes = LazyComponent(() => React.memo(() => (
|
||||
<div className={cl("no-notes")} style={{ textAlign: "center", display: "grid", placeContent: "center", height: "100%" }}>
|
||||
<Text variant="text-lg/normal">
|
||||
No Notes.
|
||||
</Text>
|
||||
</div>
|
||||
)));
|
||||
|
||||
let fistTimeOpen = true;
|
||||
|
||||
export const openNotesDataModal = async () => {
|
||||
if (fistTimeOpen) {
|
||||
cacheUsers();
|
||||
fistTimeOpen = false;
|
||||
}
|
||||
|
||||
const key = openModal(modalProps => (
|
||||
<NotesDataModal
|
||||
modalProps={modalProps}
|
||||
close={() => closeModal(key)}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
|
|
@ -1,248 +1,248 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { openPrivateChannel, openUserProfile } from "@utils/discord";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { Alerts, Avatar, Button, ContextMenuApi, Menu, React, Text, TextArea, Tooltip, useState } from "@webpack/common";
|
||||
|
||||
import { updateNote } from "../data";
|
||||
import { UsersCache } from "../types";
|
||||
import { DeleteIcon, PopupIcon, RefreshIcon, SaveIcon } from "./Icons";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
|
||||
const cl = classNameFactory("vc-notes-searcher-modal-");
|
||||
|
||||
export default ({ userId, userNotes: userNotesArg, refreshNotesData, usersCache }: {
|
||||
userId: string;
|
||||
userNotes: string;
|
||||
refreshNotesData(): void;
|
||||
usersCache: UsersCache;
|
||||
}) => {
|
||||
let userCache = usersCache.get(userId);
|
||||
|
||||
const pending = !userCache;
|
||||
|
||||
userCache ??= {
|
||||
id: userId,
|
||||
globalName: "Loading...",
|
||||
username: "Loading...",
|
||||
avatar: "https://cdn.discordapp.com/embed/avatars/4.png",
|
||||
};
|
||||
|
||||
const [userNotes, setUserNotes] = useState(userNotesArg);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cl("user")}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "80px",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
backgroundColor: "var(--background-secondary)",
|
||||
borderRadius: "12px",
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
onContextMenu={event => {
|
||||
ContextMenuApi.openContextMenu(event, () =>
|
||||
<Menu.Menu
|
||||
navId={cl("user-context-menu")}
|
||||
onClose={ContextMenuApi.closeContextMenu}
|
||||
aria-label="User Notes Data"
|
||||
>
|
||||
<Menu.MenuItem
|
||||
id={cl("open-user-profile")}
|
||||
label="Open User Profile"
|
||||
action={() => openUserProfile(userId)}
|
||||
/>
|
||||
<Menu.MenuItem
|
||||
id={cl("open-user-chat")}
|
||||
label="Open User Chat"
|
||||
action={() => openPrivateChannel(userId)}
|
||||
/>
|
||||
<Menu.MenuItem
|
||||
id={cl("copy-user-id")}
|
||||
label="Copy ID"
|
||||
action={() => copyWithToast(userCache!.id)}
|
||||
/>
|
||||
{
|
||||
!pending &&
|
||||
(
|
||||
<>
|
||||
<Menu.MenuItem
|
||||
id={cl("copy-user-globalname")}
|
||||
label="Copy Display Name"
|
||||
action={() => copyWithToast(userCache!.globalName ?? userCache!.username)}
|
||||
/>
|
||||
<Menu.MenuItem
|
||||
id={cl("copy-user-username")}
|
||||
label="Copy Username"
|
||||
action={() => copyWithToast(userCache!.username)}
|
||||
/>
|
||||
<Menu.MenuItem
|
||||
id={cl("copy-user-avatar")}
|
||||
label="Copy Avatar URL"
|
||||
action={() => copyWithToast(userCache!.avatar)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<Menu.MenuItem
|
||||
id={cl("copy-user-notes")}
|
||||
label="Copy Note"
|
||||
action={() => copyWithToast(userNotes)}
|
||||
/>
|
||||
</Menu.Menu>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{
|
||||
pending ? <LoadingSpinner /> :
|
||||
<Avatar
|
||||
className={cl("user-avatar")}
|
||||
size="SIZE_56"
|
||||
src={userCache.avatar}
|
||||
/>
|
||||
}
|
||||
<div className={cl("user-info")} style={{
|
||||
minWidth: "50px",
|
||||
maxWidth: "275px",
|
||||
width: "100%",
|
||||
}}>
|
||||
<Text className={cl("user-info-globalname")} variant="text-lg/bold" style={{
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
color: "#fff"
|
||||
}}>{userCache.globalName}</Text>
|
||||
<Text className={cl("user-info-username")} variant="text-md/normal" style={{
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
color: "#d3d3d3"
|
||||
}}>{userCache.username}</Text>
|
||||
<Text className={cl("user-info-id")} variant="text-md/normal" style={{
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
color: "#989898"
|
||||
}}>{userCache.id}</Text>
|
||||
</div>
|
||||
<div className={cl("user-notes-container")} style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "calc(100% - 86px) min-content",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
flexGrow: "1",
|
||||
paddingRight: "8px",
|
||||
gap: "8px",
|
||||
}}>
|
||||
<TextArea
|
||||
className={cl("user-text-area")}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
placeholder="Click to add a note"
|
||||
value={userNotes}
|
||||
onChange={setUserNotes}
|
||||
spellCheck={false}
|
||||
/>
|
||||
<div className={cl("user-actions")} style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "auto auto",
|
||||
gridTemplateRows: "auto auto",
|
||||
gap: "3px",
|
||||
aspectRatio: "1 / 1",
|
||||
height: "auto",
|
||||
boxSizing: "border-box",
|
||||
overflow: "visible !important",
|
||||
}}>
|
||||
<Tooltip text={"Save"}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<Button
|
||||
className={cl("user-actions-save")}
|
||||
size={Button.Sizes.NONE}
|
||||
color={Button.Colors.GREEN}
|
||||
style={{ width: "32px", height: "32px" }}
|
||||
onClick={() => {
|
||||
updateNote(userId, userNotes);
|
||||
refreshNotesData();
|
||||
}}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
>
|
||||
<SaveIcon />
|
||||
</Button>
|
||||
)}
|
||||
</Tooltip>
|
||||
<Tooltip text={"Delete"}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<Button
|
||||
className={cl("user-actions-delete")}
|
||||
size={Button.Sizes.NONE}
|
||||
color={Button.Colors.RED}
|
||||
style={{ width: "32px", height: "32px" }}
|
||||
onClick={() => {
|
||||
Alerts.show({
|
||||
title: "Delete Notes",
|
||||
body: `Are you sure you want to delete notes for ${pending ? userId : `${userCache!.globalName} (${userId})`}?`,
|
||||
confirmColor: Button.Colors.RED,
|
||||
confirmText: "Delete",
|
||||
cancelText: "Cancel",
|
||||
onConfirm: () => {
|
||||
updateNote(userId, "");
|
||||
refreshNotesData();
|
||||
},
|
||||
});
|
||||
}}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</Button>
|
||||
)}
|
||||
</Tooltip>
|
||||
<Tooltip text={"Undo text area changes"}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<Button
|
||||
className={cl("user-actions-refresh")}
|
||||
size={Button.Sizes.NONE}
|
||||
color={Button.Colors.LINK}
|
||||
style={{ width: "32px", height: "32px" }}
|
||||
onClick={() => setUserNotes(userNotesArg)}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</Button>
|
||||
)}
|
||||
</Tooltip>
|
||||
<Tooltip text={"Open User Profile"}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<Button
|
||||
className={cl("user-actions-popup")}
|
||||
size={Button.Sizes.NONE}
|
||||
color={Button.Colors.PRIMARY}
|
||||
style={{ width: "32px", height: "32px" }}
|
||||
onClick={async () => {
|
||||
openUserProfile(userId);
|
||||
}}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
>
|
||||
<PopupIcon />
|
||||
</Button>
|
||||
)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { openPrivateChannel, openUserProfile } from "@utils/discord";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { Alerts, Avatar, Button, ContextMenuApi, Menu, React, Text, TextArea, Tooltip, useState } from "@webpack/common";
|
||||
|
||||
import { updateNote } from "../data";
|
||||
import { UsersCache } from "../types";
|
||||
import { DeleteIcon, PopupIcon, RefreshIcon, SaveIcon } from "./Icons";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
|
||||
const cl = classNameFactory("vc-notes-searcher-modal-");
|
||||
|
||||
export default ({ userId, userNotes: userNotesArg, refreshNotesData, usersCache }: {
|
||||
userId: string;
|
||||
userNotes: string;
|
||||
refreshNotesData(): void;
|
||||
usersCache: UsersCache;
|
||||
}) => {
|
||||
let userCache = usersCache.get(userId);
|
||||
|
||||
const pending = !userCache;
|
||||
|
||||
userCache ??= {
|
||||
id: userId,
|
||||
globalName: "Loading...",
|
||||
username: "Loading...",
|
||||
avatar: "https://cdn.discordapp.com/embed/avatars/4.png",
|
||||
};
|
||||
|
||||
const [userNotes, setUserNotes] = useState(userNotesArg);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cl("user")}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "80px",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
backgroundColor: "var(--background-secondary)",
|
||||
borderRadius: "12px",
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
onContextMenu={event => {
|
||||
ContextMenuApi.openContextMenu(event, () =>
|
||||
<Menu.Menu
|
||||
navId={cl("user-context-menu")}
|
||||
onClose={ContextMenuApi.closeContextMenu}
|
||||
aria-label="User Notes Data"
|
||||
>
|
||||
<Menu.MenuItem
|
||||
id={cl("open-user-profile")}
|
||||
label="Open User Profile"
|
||||
action={() => openUserProfile(userId)}
|
||||
/>
|
||||
<Menu.MenuItem
|
||||
id={cl("open-user-chat")}
|
||||
label="Open User Chat"
|
||||
action={() => openPrivateChannel(userId)}
|
||||
/>
|
||||
<Menu.MenuItem
|
||||
id={cl("copy-user-id")}
|
||||
label="Copy ID"
|
||||
action={() => copyWithToast(userCache!.id)}
|
||||
/>
|
||||
{
|
||||
!pending &&
|
||||
(
|
||||
<>
|
||||
<Menu.MenuItem
|
||||
id={cl("copy-user-globalname")}
|
||||
label="Copy Display Name"
|
||||
action={() => copyWithToast(userCache!.globalName ?? userCache!.username)}
|
||||
/>
|
||||
<Menu.MenuItem
|
||||
id={cl("copy-user-username")}
|
||||
label="Copy Username"
|
||||
action={() => copyWithToast(userCache!.username)}
|
||||
/>
|
||||
<Menu.MenuItem
|
||||
id={cl("copy-user-avatar")}
|
||||
label="Copy Avatar URL"
|
||||
action={() => copyWithToast(userCache!.avatar)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<Menu.MenuItem
|
||||
id={cl("copy-user-notes")}
|
||||
label="Copy Note"
|
||||
action={() => copyWithToast(userNotes)}
|
||||
/>
|
||||
</Menu.Menu>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{
|
||||
pending ? <LoadingSpinner /> :
|
||||
<Avatar
|
||||
className={cl("user-avatar")}
|
||||
size="SIZE_56"
|
||||
src={userCache.avatar}
|
||||
/>
|
||||
}
|
||||
<div className={cl("user-info")} style={{
|
||||
minWidth: "50px",
|
||||
maxWidth: "275px",
|
||||
width: "100%",
|
||||
}}>
|
||||
<Text className={cl("user-info-globalname")} variant="text-lg/bold" style={{
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
color: "#fff"
|
||||
}}>{userCache.globalName}</Text>
|
||||
<Text className={cl("user-info-username")} variant="text-md/normal" style={{
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
color: "#d3d3d3"
|
||||
}}>{userCache.username}</Text>
|
||||
<Text className={cl("user-info-id")} variant="text-md/normal" style={{
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
color: "#989898"
|
||||
}}>{userCache.id}</Text>
|
||||
</div>
|
||||
<div className={cl("user-notes-container")} style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "calc(100% - 86px) min-content",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
flexGrow: "1",
|
||||
paddingRight: "8px",
|
||||
gap: "8px",
|
||||
}}>
|
||||
<TextArea
|
||||
className={cl("user-text-area")}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
placeholder="Click to add a note"
|
||||
value={userNotes}
|
||||
onChange={setUserNotes}
|
||||
spellCheck={false}
|
||||
/>
|
||||
<div className={cl("user-actions")} style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "auto auto",
|
||||
gridTemplateRows: "auto auto",
|
||||
gap: "3px",
|
||||
aspectRatio: "1 / 1",
|
||||
height: "auto",
|
||||
boxSizing: "border-box",
|
||||
overflow: "visible !important",
|
||||
}}>
|
||||
<Tooltip text={"Save"}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<Button
|
||||
className={cl("user-actions-save")}
|
||||
size={Button.Sizes.NONE}
|
||||
color={Button.Colors.GREEN}
|
||||
style={{ width: "32px", height: "32px" }}
|
||||
onClick={() => {
|
||||
updateNote(userId, userNotes);
|
||||
refreshNotesData();
|
||||
}}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
>
|
||||
<SaveIcon />
|
||||
</Button>
|
||||
)}
|
||||
</Tooltip>
|
||||
<Tooltip text={"Delete"}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<Button
|
||||
className={cl("user-actions-delete")}
|
||||
size={Button.Sizes.NONE}
|
||||
color={Button.Colors.RED}
|
||||
style={{ width: "32px", height: "32px" }}
|
||||
onClick={() => {
|
||||
Alerts.show({
|
||||
title: "Delete Notes",
|
||||
body: `Are you sure you want to delete notes for ${pending ? userId : `${userCache!.globalName} (${userId})`}?`,
|
||||
confirmColor: Button.Colors.RED,
|
||||
confirmText: "Delete",
|
||||
cancelText: "Cancel",
|
||||
onConfirm: () => {
|
||||
updateNote(userId, "");
|
||||
refreshNotesData();
|
||||
},
|
||||
});
|
||||
}}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</Button>
|
||||
)}
|
||||
</Tooltip>
|
||||
<Tooltip text={"Undo text area changes"}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<Button
|
||||
className={cl("user-actions-refresh")}
|
||||
size={Button.Sizes.NONE}
|
||||
color={Button.Colors.LINK}
|
||||
style={{ width: "32px", height: "32px" }}
|
||||
onClick={() => setUserNotes(userNotesArg)}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</Button>
|
||||
)}
|
||||
</Tooltip>
|
||||
<Tooltip text={"Open User Profile"}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<Button
|
||||
className={cl("user-actions-popup")}
|
||||
size={Button.Sizes.NONE}
|
||||
color={Button.Colors.PRIMARY}
|
||||
style={{ width: "32px", height: "32px" }}
|
||||
onClick={async () => {
|
||||
openUserProfile(userId);
|
||||
}}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
>
|
||||
<PopupIcon />
|
||||
</Button>
|
||||
)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,153 +1,153 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Constants, FluxDispatcher, GuildStore, RestAPI, SnowflakeUtils, UserStore, UserUtils } from "@webpack/common";
|
||||
import { waitForStore } from "webpack/common/internal";
|
||||
|
||||
import { refreshNotesData, refreshUsersCache } from "./components/NotesDataModal";
|
||||
import * as t from "./types";
|
||||
|
||||
let NoteStore: t.NoteStore;
|
||||
|
||||
waitForStore("NoteStore", s => NoteStore = s);
|
||||
|
||||
export const getNotes = () => {
|
||||
return NoteStore.getNotes();
|
||||
};
|
||||
|
||||
export const onNoteUpdate = () => {
|
||||
refreshNotesData();
|
||||
};
|
||||
|
||||
export const updateNote = (userId: string, note: string | null) => {
|
||||
RestAPI.put({
|
||||
url: Constants.Endpoints.NOTE(userId),
|
||||
body: { note: note },
|
||||
oldFormErrors: true
|
||||
});
|
||||
};
|
||||
|
||||
export const usersCache: t.UsersCache = new Map();
|
||||
|
||||
export const onUserUpdate = ({ user }: { user: t.User; }) => {
|
||||
if (!getNotes()[user.id]) return;
|
||||
|
||||
// doesn't have .getAvatarURL
|
||||
const userFromStore = UserStore.getUser(user.id);
|
||||
|
||||
if (!userFromStore) return;
|
||||
|
||||
cacheUser(userFromStore);
|
||||
};
|
||||
|
||||
const fetchUser = async (userId: string) => {
|
||||
for (let _ = 0; _ < 5; _++) {
|
||||
try {
|
||||
return await UserUtils.getUser(userId);
|
||||
} catch (error: any) {
|
||||
const wait = error?.body?.retry_after;
|
||||
|
||||
if (!wait) return;
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, wait * 1000 + 100));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const cacheUser = (user: t.User) => {
|
||||
usersCache.set(user.id, {
|
||||
id: user.id,
|
||||
globalName: user.globalName ?? user.username,
|
||||
username: user.username,
|
||||
avatar: user.getAvatarURL(void 0, void 0, false),
|
||||
});
|
||||
};
|
||||
|
||||
export const cacheUsers = async () => {
|
||||
const toRequest: string[] = [];
|
||||
|
||||
for (const userId of Object.keys(getNotes())) {
|
||||
const user = UserStore.getUser(userId);
|
||||
|
||||
if (user) {
|
||||
cacheUser(user);
|
||||
continue;
|
||||
}
|
||||
|
||||
toRequest.push(userId);
|
||||
}
|
||||
|
||||
if (usersCache.size >= Object.keys(getNotes()).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sentNonce = SnowflakeUtils.fromTimestamp(Date.now());
|
||||
|
||||
const allGuildIds = Object.keys(GuildStore.getGuilds());
|
||||
let count = allGuildIds.length * Math.ceil(toRequest.length / 100);
|
||||
|
||||
const processed = new Set<string>();
|
||||
|
||||
const callback = async ({ chunks }) => {
|
||||
for (const chunk of chunks) {
|
||||
const { nonce, members }: {
|
||||
nonce: string;
|
||||
members: {
|
||||
user: t.User;
|
||||
}[];
|
||||
} = chunk;
|
||||
|
||||
if (nonce !== sentNonce) {
|
||||
return;
|
||||
}
|
||||
|
||||
members.forEach(({ user }) => {
|
||||
if (processed.has(user.id)) return;
|
||||
|
||||
processed.add(user.id);
|
||||
|
||||
cacheUser(UserStore.getUser(user.id));
|
||||
});
|
||||
|
||||
refreshUsersCache();
|
||||
|
||||
if (--count === 0) {
|
||||
FluxDispatcher.unsubscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);
|
||||
|
||||
const userIds = Object.keys(getNotes());
|
||||
|
||||
if (usersCache.size !== userIds.length) {
|
||||
|
||||
for (const userId of userIds) {
|
||||
if (usersCache.has(userId)) continue;
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
const user = await fetchUser(userId);
|
||||
|
||||
if (user) {
|
||||
cacheUser(user);
|
||||
refreshUsersCache();
|
||||
}
|
||||
}
|
||||
|
||||
} else
|
||||
refreshUsersCache();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
FluxDispatcher.subscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);
|
||||
|
||||
for (let i = 0; i < toRequest.length; i += 100) {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "GUILD_MEMBERS_REQUEST",
|
||||
guildIds: allGuildIds,
|
||||
userIds: toRequest.slice(i, i + 100),
|
||||
nonce: sentNonce,
|
||||
});
|
||||
}
|
||||
};
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Constants, FluxDispatcher, GuildStore, RestAPI, SnowflakeUtils, UserStore, UserUtils } from "@webpack/common";
|
||||
import { waitForStore } from "webpack/common/internal";
|
||||
|
||||
import { refreshNotesData, refreshUsersCache } from "./components/NotesDataModal";
|
||||
import * as t from "./types";
|
||||
|
||||
let NoteStore: t.NoteStore;
|
||||
|
||||
waitForStore("NoteStore", s => NoteStore = s);
|
||||
|
||||
export const getNotes = () => {
|
||||
return NoteStore.getNotes();
|
||||
};
|
||||
|
||||
export const onNoteUpdate = () => {
|
||||
refreshNotesData();
|
||||
};
|
||||
|
||||
export const updateNote = (userId: string, note: string | null) => {
|
||||
RestAPI.put({
|
||||
url: Constants.Endpoints.NOTE(userId),
|
||||
body: { note: note },
|
||||
oldFormErrors: true
|
||||
});
|
||||
};
|
||||
|
||||
export const usersCache: t.UsersCache = new Map();
|
||||
|
||||
export const onUserUpdate = ({ user }: { user: t.User; }) => {
|
||||
if (!getNotes()[user.id]) return;
|
||||
|
||||
// doesn't have .getAvatarURL
|
||||
const userFromStore = UserStore.getUser(user.id);
|
||||
|
||||
if (!userFromStore) return;
|
||||
|
||||
cacheUser(userFromStore);
|
||||
};
|
||||
|
||||
const fetchUser = async (userId: string) => {
|
||||
for (let _ = 0; _ < 5; _++) {
|
||||
try {
|
||||
return await UserUtils.getUser(userId);
|
||||
} catch (error: any) {
|
||||
const wait = error?.body?.retry_after;
|
||||
|
||||
if (!wait) return;
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, wait * 1000 + 100));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const cacheUser = (user: t.User) => {
|
||||
usersCache.set(user.id, {
|
||||
id: user.id,
|
||||
globalName: user.globalName ?? user.username,
|
||||
username: user.username,
|
||||
avatar: user.getAvatarURL(void 0, void 0, false),
|
||||
});
|
||||
};
|
||||
|
||||
export const cacheUsers = async () => {
|
||||
const toRequest: string[] = [];
|
||||
|
||||
for (const userId of Object.keys(getNotes())) {
|
||||
const user = UserStore.getUser(userId);
|
||||
|
||||
if (user) {
|
||||
cacheUser(user);
|
||||
continue;
|
||||
}
|
||||
|
||||
toRequest.push(userId);
|
||||
}
|
||||
|
||||
if (usersCache.size >= Object.keys(getNotes()).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sentNonce = SnowflakeUtils.fromTimestamp(Date.now());
|
||||
|
||||
const allGuildIds = Object.keys(GuildStore.getGuilds());
|
||||
let count = allGuildIds.length * Math.ceil(toRequest.length / 100);
|
||||
|
||||
const processed = new Set<string>();
|
||||
|
||||
const callback = async ({ chunks }) => {
|
||||
for (const chunk of chunks) {
|
||||
const { nonce, members }: {
|
||||
nonce: string;
|
||||
members: {
|
||||
user: t.User;
|
||||
}[];
|
||||
} = chunk;
|
||||
|
||||
if (nonce !== sentNonce) {
|
||||
return;
|
||||
}
|
||||
|
||||
members.forEach(({ user }) => {
|
||||
if (processed.has(user.id)) return;
|
||||
|
||||
processed.add(user.id);
|
||||
|
||||
cacheUser(UserStore.getUser(user.id));
|
||||
});
|
||||
|
||||
refreshUsersCache();
|
||||
|
||||
if (--count === 0) {
|
||||
FluxDispatcher.unsubscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);
|
||||
|
||||
const userIds = Object.keys(getNotes());
|
||||
|
||||
if (usersCache.size !== userIds.length) {
|
||||
|
||||
for (const userId of userIds) {
|
||||
if (usersCache.has(userId)) continue;
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
const user = await fetchUser(userId);
|
||||
|
||||
if (user) {
|
||||
cacheUser(user);
|
||||
refreshUsersCache();
|
||||
}
|
||||
}
|
||||
|
||||
} else
|
||||
refreshUsersCache();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
FluxDispatcher.subscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);
|
||||
|
||||
for (let i = 0; i < toRequest.length; i += 100) {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "GUILD_MEMBERS_REQUEST",
|
||||
guildIds: allGuildIds,
|
||||
userIds: toRequest.slice(i, i + 100),
|
||||
nonce: sentNonce,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
*/
|
||||
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import styles from "./styles.css?managed";
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
@ -15,6 +13,7 @@ import { FluxDispatcher } from "@webpack/common";
|
|||
import { OpenNotesDataButton } from "./components/NotesDataButton";
|
||||
import { getNotes, onNoteUpdate, onUserUpdate } from "./data";
|
||||
import settings from "./settings";
|
||||
import styles from "./styles.css?managed";
|
||||
import { Notes } from "./types";
|
||||
|
||||
export default definePlugin({
|
||||
|
|
|
@ -1,107 +1,107 @@
|
|||
.vc-notes-searcher-toolbox-button svg {
|
||||
color: var(--interactive-normal);
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-user-actions * svg {
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-toolbox-button:hover svg,
|
||||
.vc-notes-searcher-toolbox-button[class*="selected"] svg {
|
||||
color: var(--interactive-active);
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-root {
|
||||
min-height: 75vh;
|
||||
max-height: 75vh;
|
||||
min-width: 70vw;
|
||||
max-width: 70vw;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-header-input {
|
||||
width: 100%;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-content {
|
||||
padding-bottom: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-content div[aria-hidden="true"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-content-inner > *:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-user:hover:not(
|
||||
:has(
|
||||
.vc-notes-searcher-modal-user-text-area:hover,
|
||||
.vc-notes-searcher-modal-user-actions:hover
|
||||
)
|
||||
) {
|
||||
background-color: var(--background-secondary-alt);
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-user-avatar {
|
||||
aspect-ratio: 1 / 1;
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-user-actions * div:has(svg) {
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-user-notes-container
|
||||
*
|
||||
div:has(.vc-notes-searcher-modal-user-text-area) {
|
||||
height: 67px;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-user-text-area {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-spinner::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border: 5px solid #fff;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
animation: vc-notes-searcher-scale-up 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes vc-notes-searcher-scale-up {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) scale(0);
|
||||
}
|
||||
|
||||
60%,
|
||||
100% {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vc-notes-searcher-pulse {
|
||||
0%,
|
||||
60%,
|
||||
100% {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
.vc-notes-searcher-toolbox-button svg {
|
||||
color: var(--interactive-normal);
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-user-actions * svg {
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-toolbox-button:hover svg,
|
||||
.vc-notes-searcher-toolbox-button[class*="selected"] svg {
|
||||
color: var(--interactive-active);
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-root {
|
||||
min-height: 75vh;
|
||||
max-height: 75vh;
|
||||
min-width: 70vw;
|
||||
max-width: 70vw;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-header-input {
|
||||
width: 100%;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-content {
|
||||
padding-bottom: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-content div[aria-hidden="true"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-content-inner > *:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-user:hover:not(
|
||||
:has(
|
||||
.vc-notes-searcher-modal-user-text-area:hover,
|
||||
.vc-notes-searcher-modal-user-actions:hover
|
||||
)
|
||||
) {
|
||||
background-color: var(--background-secondary-alt);
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-user-avatar {
|
||||
aspect-ratio: 1 / 1;
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-user-actions * div:has(svg) {
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-user-notes-container
|
||||
*
|
||||
div:has(.vc-notes-searcher-modal-user-text-area) {
|
||||
height: 67px;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-user-text-area {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vc-notes-searcher-modal-spinner::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border: 5px solid #fff;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
animation: vc-notes-searcher-scale-up 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes vc-notes-searcher-scale-up {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) scale(0);
|
||||
}
|
||||
|
||||
60%,
|
||||
100% {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vc-notes-searcher-pulse {
|
||||
0%,
|
||||
60%,
|
||||
100% {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue