From ffe6512693c7828c1d3c3bb36db69d69a93abe3f Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 22 Nov 2023 02:49:08 -0300 Subject: [PATCH] Improve component finding api and migrate plugins to use them --- src/components/PluginSettings/PluginModal.tsx | 6 +- src/plugins/betterFolders/FolderSideBar.tsx | 6 +- src/plugins/clientTheme/index.tsx | 5 +- src/plugins/customRPC/index.tsx | 6 +- src/plugins/gameActivityToggle/index.tsx | 4 +- src/plugins/messageLinkEmbeds/index.tsx | 10 +-- .../serverProfile/GuildProfileModal.tsx | 6 +- src/plugins/showConnections/VerifiedIcon.tsx | 6 +- src/plugins/showConnections/index.tsx | 6 +- .../components/HiddenChannelLockScreen.tsx | 10 +-- src/plugins/voiceMessages/VoicePreview.tsx | 5 +- src/plugins/whoReacted/index.tsx | 6 +- src/utils/react.tsx | 68 ++++++++++++++----- src/webpack/webpack.ts | 2 +- 14 files changed, 89 insertions(+), 57 deletions(-) diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 03789ac6..7d5750df 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -24,9 +24,9 @@ import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; import { classes, isObjectEmpty } from "@utils/misc"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy } from "@utils/react"; import { OptionType, Plugin } from "@utils/types"; -import { findByCode, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; import { User } from "discord-types/general"; import { Constructor } from "type-fest"; @@ -42,7 +42,7 @@ import { } from "./components"; import { openContributorModal } from "./ContributorModal"; -const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); +const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); const UserRecord: Constructor> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any; diff --git a/src/plugins/betterFolders/FolderSideBar.tsx b/src/plugins/betterFolders/FolderSideBar.tsx index 97959873..13dd9ac8 100644 --- a/src/plugins/betterFolders/FolderSideBar.tsx +++ b/src/plugins/betterFolders/FolderSideBar.tsx @@ -17,8 +17,8 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import { LazyComponent } from "@utils/react"; -import { find, findByPropsLazy, findStoreLazy } from "@webpack"; +import { findComponentByCodeLazy } from "@utils/react"; +import { findByPropsLazy, findStoreLazy } from "@webpack"; import { useStateFromStores } from "@webpack/common"; import type { CSSProperties } from "react"; @@ -26,7 +26,7 @@ import { ExpandedGuildFolderStore, settings } from "."; const ChannelRTCStore = findStoreLazy("ChannelRTCStore"); const Animations = findByPropsLazy("a", "animated", "useTransition"); -const GuildsBar = LazyComponent(() => find(m => m.type?.toString().includes('("guildsnav")'))); +const GuildsBar = findComponentByCodeLazy('("guildsnav")'); export default ErrorBoundary.wrap(guildsBarProps => { const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders()); diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index 7cda33e2..e5de67e4 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -11,12 +11,11 @@ import { Devs } from "@utils/constants"; import { getTheme, Theme } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType, StartAt } from "@utils/types"; -import { findByCode } from "@webpack"; import { Button, Forms } from "@webpack/common"; -const ColorPicker = LazyComponent(() => findByCode(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR")); +const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR"); const colorPresets = [ "#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D", diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx index feed52fd..64bfd460 100644 --- a/src/plugins/customRPC/index.tsx +++ b/src/plugins/customRPC/index.tsx @@ -20,12 +20,12 @@ import { definePluginSettings, Settings } from "@api/Settings"; import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; import { isTruthy } from "@utils/guards"; -import { useAwaiter } from "@utils/react"; +import { findComponentByCodeLazy, useAwaiter } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { findByCodeLazy, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { ApplicationAssetUtils, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common"; -const ActivityComponent = findByCodeLazy("onOpenGameProfile"); +const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile"); const ActivityClassName = findByPropsLazy("activity", "buttonColor"); const Colors = findByPropsLazy("profileColors"); diff --git a/src/plugins/gameActivityToggle/index.tsx b/src/plugins/gameActivityToggle/index.tsx index 735f124c..cc68d55b 100644 --- a/src/plugins/gameActivityToggle/index.tsx +++ b/src/plugins/gameActivityToggle/index.tsx @@ -19,13 +19,13 @@ import { disableStyle, enableStyle } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; +import { findComponentByCodeLazy } from "@utils/react"; import definePlugin from "@utils/types"; -import { findByCodeLazy } from "@webpack"; import { StatusSettingsStores } from "@webpack/common"; import style from "./style.css?managed"; -const Button = findByCodeLazy("Button.Sizes.NONE,disabled:"); +const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:"); function makeIcon(showCurrentGame?: boolean) { return function () { diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index e600d737..d8c3c99f 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -22,9 +22,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants.js"; import { classes } from "@utils/misc"; import { Queue } from "@utils/Queue"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { find, findByCode, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { Button, ChannelStore, @@ -45,9 +45,9 @@ const messageCache = new Map(); -const Embed = LazyComponent(() => findByCode(".inlineMediaEmbed")); -const AutoModEmbed = LazyComponent(() => findByCode(".withFooter]:", "childrenMessageContent:")); -const ChannelMessage = LazyComponent(() => find(m => m.type?.toString()?.includes("renderSimpleAccessories)"))); +const Embed = findComponentByCodeLazy(".inlineMediaEmbed"); +const AutoModEmbed = findComponentByCodeLazy(".withFooter]:", "childrenMessageContent:"); +const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)"); const SearchResultClasses = findByPropsLazy("message", "searchResult"); diff --git a/src/plugins/serverProfile/GuildProfileModal.tsx b/src/plugins/serverProfile/GuildProfileModal.tsx index 80f0842b..9def7bf9 100644 --- a/src/plugins/serverProfile/GuildProfileModal.tsx +++ b/src/plugins/serverProfile/GuildProfileModal.tsx @@ -10,14 +10,14 @@ import { classNameFactory } from "@api/Styles"; import { openImageModal, openUserProfile } from "@utils/discord"; import { classes } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { LazyComponent, useAwaiter } from "@utils/react"; -import { findByProps, findByPropsLazy } from "@webpack"; +import { findExportedComponentLazy, useAwaiter } from "@utils/react"; +import { findByPropsLazy } from "@webpack"; import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, moment, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; import { Guild, User } from "discord-types/general"; const IconUtils = findByPropsLazy("getGuildBannerURL"); const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper"); -const FriendRow = LazyComponent(() => findByProps("FriendRow").FriendRow); +const FriendRow = findExportedComponentLazy("FriendRow"); const cl = classNameFactory("vc-gp-"); diff --git a/src/plugins/showConnections/VerifiedIcon.tsx b/src/plugins/showConnections/VerifiedIcon.tsx index 79e27c45..20005069 100644 --- a/src/plugins/showConnections/VerifiedIcon.tsx +++ b/src/plugins/showConnections/VerifiedIcon.tsx @@ -16,12 +16,12 @@ * along with this program. If not, see . */ -import { LazyComponent } from "@utils/react"; -import { findByCode, findLazy } from "@webpack"; +import { findComponentByCodeLazy } from "@utils/react"; +import { findLazy } from "@webpack"; import { i18n, useToken } from "@webpack/common"; const ColorMap = findLazy(m => m.colors?.INTERACTIVE_MUTED?.css); -const VerifiedIconComponent = LazyComponent(() => findByCode(".CONNECTIONS_ROLE_OFFICIAL_ICON_TOOLTIP")); +const VerifiedIconComponent = findComponentByCodeLazy(".CONNECTIONS_ROLE_OFFICIAL_ICON_TOOLTIP"); export function VerifiedIcon() { const color = useToken(ColorMap.colors.INTERACTIVE_MUTED).hex(); diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index 948bdb83..2d2122d6 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -24,15 +24,15 @@ import { Flex } from "@components/Flex"; import { CopyIcon, LinkIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { copyWithToast } from "@utils/misc"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { findByCode, findByCodeLazy, findByPropsLazy, findStoreLazy } from "@webpack"; +import { findByCodeLazy, findByPropsLazy, findStoreLazy } from "@webpack"; import { Text, Tooltip, UserProfileStore } from "@webpack/common"; import { User } from "discord-types/general"; import { VerifiedIcon } from "./VerifiedIcon"; -const Section = LazyComponent(() => findByCode(".lastSection]:")); +const Section = findComponentByCodeLazy(".lastSection]: "); const ThemeStore = findStoreLazy("ThemeStore"); const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl"); const getTheme: (user: User, displayProfile: any) => any = findByCodeLazy(',"--profile-gradient-primary-color"'); diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index 26efce1d..5231fe8e 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -18,9 +18,9 @@ import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy, findComponentLazy } from "@utils/react"; import { formatDuration } from "@utils/text"; -import { find, findByCode, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; import type { Channel } from "discord-types/general"; @@ -81,14 +81,14 @@ const enum ChannelFlags { const ChatScrollClasses = findByPropsLazy("auto", "content", "scrollerBase"); const ChatClasses = findByPropsLazy("chat", "content", "noChat", "chatContent"); -const ChannelBeginHeader = LazyComponent(() => findByCode(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE")); -const TagComponent = LazyComponent(() => find(m => { +const ChannelBeginHeader = findComponentByCodeLazy(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE"); +const TagComponent = findComponentLazy(m => { if (typeof m !== "function") return false; const code = Function.prototype.toString.call(m); // Get the component which doesn't include increasedActivity return code.includes(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG") && !code.includes("increasedActivityPill"); -})); +}); const EmojiParser = findByPropsLazy("convertSurrogateToName"); const EmojiUtils = findByPropsLazy("getURL", "buildEmojiReactionColorsPlatformed"); diff --git a/src/plugins/voiceMessages/VoicePreview.tsx b/src/plugins/voiceMessages/VoicePreview.tsx index 912c55ae..d2c62336 100644 --- a/src/plugins/voiceMessages/VoicePreview.tsx +++ b/src/plugins/voiceMessages/VoicePreview.tsx @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -import { LazyComponent, useTimer } from "@utils/react"; -import { find } from "@webpack"; +import { findComponentByCodeLazy, useTimer } from "@utils/react"; import { cl } from "./utils"; @@ -25,7 +24,7 @@ interface VoiceMessageProps { src: string; waveform: string; } -const VoiceMessage = LazyComponent(() => find(m => m.type?.toString().includes("waveform:"))); +const VoiceMessage = findComponentByCodeLazy("waveform:"); export type VoicePreviewOptions = { src?: string; diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index fb84c155..15395a1f 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -20,14 +20,14 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { sleep } from "@utils/misc"; import { Queue } from "@utils/Queue"; -import { LazyComponent, useForceUpdater } from "@utils/react"; +import { findComponentByCodeLazy, useForceUpdater } from "@utils/react"; import definePlugin from "@utils/types"; -import { findByCode, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { ChannelStore, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common"; import { CustomEmoji } from "@webpack/types"; import { Message, ReactionEmoji, User } from "discord-types/general"; -const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); +const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); const queue = new Queue(); diff --git a/src/utils/react.tsx b/src/utils/react.tsx index 0181c95b..2e3352bc 100644 --- a/src/utils/react.tsx +++ b/src/utils/react.tsx @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { FilterFn, filters, find, findByProps } from "@webpack"; import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common"; import { makeLazy } from "./lazy"; @@ -77,7 +78,6 @@ interface AwaiterOpts { * @param fallbackValue The fallback value that will be used until the promise resolved * @returns [value, error, isPending] */ - export function useAwaiter(factory: () => Promise): AwaiterRes; export function useAwaiter(factory: () => Promise, providedOpts: AwaiterOpts): AwaiterRes; export function useAwaiter(factory: () => Promise, providedOpts?: AwaiterOpts): AwaiterRes { @@ -113,31 +113,16 @@ export function useAwaiter(factory: () => Promise, providedOpts?: AwaiterO return [state.value, state.error, state.pending]; } + /** * Returns a function that can be used to force rerender react components */ - export function useForceUpdater(): () => void; export function useForceUpdater(withDep: true): [unknown, () => void]; export function useForceUpdater(withDep?: true) { const r = useReducer(x => x + 1, 0); return withDep ? r : r[1]; } -/** - * A lazy component. The factory method is called on first render. For example useful - * for const Component = LazyComponent(() => findByDisplayName("...").default) - * @param factory Function returning a Component - * @param attempts How many times to try to get the component before giving up - * @returns Result of factory function - */ - -export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { - const get = makeLazy(factory, attempts); - return (props: T) => { - const Component = get() ?? NoopComponent; - return ; - }; -} interface TimerOpts { interval?: number; @@ -159,3 +144,52 @@ export function useTimer({ interval = 1000, deps = [] }: TimerOpts) { return time; } + +/** + * Finds the component which includes all the given code. Checks for plain components, memos and forwardRefs + */ +export function findComponentByCode(...code: string[]) { + const filter = filters.byCode(...code); + return find(m => { + if (filter(m)) return true; + if (!m.$$typeof) return false; + if (m.type) return filter(m.type); // memos + if (m.render) return filter(m.render); // forwardRefs + return false; + }) ?? NoopComponent; +} + +/** + * Finds the first component that matches the filter, lazily. + */ +export function findComponentLazy(filter: FilterFn) { + return LazyComponent(() => find(filter)); +} + +/** + * Finds the first component that includes all the given code, lazily + */ +export function findComponentByCodeLazy(...code: string[]) { + return LazyComponent(() => findComponentByCode(...code)); +} + +/** + * Finds the first component that is exported by the first prop name, lazily + */ +export function findExportedComponentLazy(...props: string[]) { + return LazyComponent(() => findByProps(...props)?.[props[0]]); +} + +/** + * A lazy component. The factory method is called on first render. + * @param factory Function returning a Component + * @param attempts How many times to try to get the component before giving up + * @returns Result of factory function + */ +export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { + const get = makeLazy(factory, attempts); + return (props: T) => { + const Component = get() ?? NoopComponent; + return ; + }; +} diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 3ef5ac80..4fb94056 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -386,7 +386,7 @@ export function findStore(name: string) { } /** - * findByDisplayName but lazy + * findStore but lazy */ export function findStoreLazy(name: string) { return proxyLazy(() => findStore(name));