From 3f51ee1b2aef0a9e94a97eed35ff6d0d81aacd48 Mon Sep 17 00:00:00 2001 From: V Date: Tue, 15 Jul 2025 15:57:24 +0200 Subject: [PATCH] refactor Settings UI (#3545) Much improved file structure and cleaner code. Also gets rid of temporary settings & saving and instead applies all changes immediately. Besides that, this change only changes code and doesn't change the ui --- .gitignore | 2 +- packages/discord-types/src/components.d.ts | 2 + src/Vencord.ts | 8 +- src/api/Badges.ts | 5 +- src/api/MessageUpdater.ts | 4 +- src/api/Notifications/notificationLog.tsx | 2 +- src/components/Icons.tsx | 1 - .../PluginSettings/components/index.ts | 43 -- .../VencordSettings/PatchHelperTab.tsx | 393 ----------------- src/components/VencordSettings/PluginsTab.tsx | 23 - src/components/VencordSettings/ThemesTab.tsx | 402 ------------------ src/components/VencordSettings/UpdaterTab.tsx | 268 ------------ src/components/VencordSettings/VencordTab.tsx | 301 ------------- src/components/index.ts | 6 +- .../addonCard.css => settings/AddonCard.css} | 0 .../AddonCard.tsx | 11 +- .../{ => settings}/DonateButton.tsx | 3 +- .../{Badge.tsx => settings/PluginBadge.tsx} | 4 +- .../QuickAction.css} | 0 .../QuickAction.tsx} | 2 +- .../SpecialCard.css} | 0 .../SpecialCard.tsx | 2 +- src/components/{ => settings}/Switch.css | 0 src/components/{ => settings}/Switch.tsx | 0 src/components/settings/index.ts | 13 + .../shared.tsx => settings/tabs/BaseTab.tsx} | 3 - src/components/settings/tabs/index.ts | 18 + .../tabs/patchHelper/FullPatchInput.tsx | 83 ++++ .../tabs/patchHelper/PatchPreview.tsx | 149 +++++++ .../tabs/patchHelper/ReplacementInput.tsx | 83 ++++ .../settings/tabs/patchHelper/index.tsx | 165 +++++++ .../tabs/plugins/ContributorModal.css} | 0 .../tabs/plugins}/ContributorModal.tsx | 4 +- .../tabs/plugins}/LinkIconButton.css | 0 .../tabs/plugins}/LinkIconButton.tsx | 3 +- .../settings/tabs/plugins/PluginCard.tsx | 110 +++++ .../tabs/plugins}/PluginModal.css | 0 .../tabs/plugins}/PluginModal.tsx | 185 ++------ .../plugins/components/BooleanSetting.tsx} | 24 +- .../tabs/plugins/components/Common.tsx | 48 +++ .../plugins/components/ComponentSetting.tsx} | 6 +- .../plugins/components/NumberSetting.tsx} | 35 +- .../plugins/components/SelectSetting.tsx} | 35 +- .../plugins/components/SliderSetting.tsx} | 36 +- .../tabs/plugins/components/TextSetting.tsx} | 34 +- .../settings/tabs/plugins/components/index.ts | 39 ++ .../tabs/plugins}/index.tsx | 231 ++++------ .../tabs/plugins}/styles.css | 2 +- .../tabs/styles.css} | 0 .../tabs/sync}/BackupAndRestoreTab.tsx | 7 +- .../tabs/sync}/CloudTab.tsx | 3 +- .../settings/tabs/themes/CspErrorCard.tsx | 86 ++++ .../settings/tabs/themes/LocalThemesTab.tsx | 170 ++++++++ .../settings/tabs/themes/OnlineThemesTab.tsx | 56 +++ .../settings/tabs/themes/ThemeCard.tsx | 56 +++ src/components/settings/tabs/themes/index.tsx | 85 ++++ .../tabs/themes/styles.css} | 0 .../settings/tabs/updater/Components.tsx | 148 +++++++ .../settings/tabs/updater/index.tsx | 115 +++++ .../settings/tabs/updater/runWithDispatch.tsx | 50 +++ .../settings/tabs/vencord/DonateButton.tsx | 25 ++ .../tabs/vencord/MacVibrancySettings.tsx | 80 ++++ .../tabs/vencord}/NotificationSettings.tsx | 52 ++- .../settings/tabs/vencord/index.tsx | 211 +++++++++ src/plugins/_api/badges/index.tsx | 4 +- src/plugins/_core/settings.tsx | 12 +- src/plugins/_core/supportHelper.tsx | 4 +- src/plugins/appleMusic.desktop/index.tsx | 4 +- src/plugins/betterSettings/PluginsSubmenu.tsx | 2 +- src/plugins/consoleJanitor/index.tsx | 3 +- src/plugins/ctrlEnterSend/index.ts | 4 +- src/plugins/customIdle/index.ts | 3 +- .../decor/ui/modals/ChangeDecorationModal.tsx | 5 +- src/plugins/experiments/index.tsx | 7 +- src/plugins/fixSpotifyEmbeds.desktop/index.ts | 3 +- src/plugins/imageZoom/index.tsx | 3 +- src/plugins/quickReply/index.ts | 7 +- src/plugins/roleColorEverywhere/index.tsx | 3 +- .../components/Highlighter.tsx | 4 +- .../hooks/useShikiSettings.ts | 25 +- src/plugins/shikiCodeblocks.desktop/index.ts | 5 +- src/plugins/showConnections/index.tsx | 4 +- src/plugins/typingIndicator/index.tsx | 3 +- src/plugins/unlockedAvatarZoom/index.ts | 3 +- src/plugins/userVoiceShow/components.tsx | 3 +- src/plugins/vcNarrator/index.tsx | 22 +- src/plugins/volumeBooster/index.ts | 3 +- src/plugins/webKeybinds.web/index.ts | 4 +- src/plugins/whoReacted/index.tsx | 5 +- src/plugins/xsOverlay/index.tsx | 3 +- src/utils/constants.ts | 5 + src/utils/react.tsx | 7 + src/utils/types.ts | 29 +- src/webpack/common/components.ts | 1 + 94 files changed, 2120 insertions(+), 2002 deletions(-) delete mode 100644 src/components/PluginSettings/components/index.ts delete mode 100644 src/components/VencordSettings/PatchHelperTab.tsx delete mode 100644 src/components/VencordSettings/PluginsTab.tsx delete mode 100644 src/components/VencordSettings/ThemesTab.tsx delete mode 100644 src/components/VencordSettings/UpdaterTab.tsx delete mode 100644 src/components/VencordSettings/VencordTab.tsx rename src/components/{VencordSettings/addonCard.css => settings/AddonCard.css} (100%) rename src/components/{VencordSettings => settings}/AddonCard.tsx (93%) rename src/components/{ => settings}/DonateButton.tsx (96%) rename src/components/{Badge.tsx => settings/PluginBadge.tsx} (90%) rename src/components/{VencordSettings/quickActions.css => settings/QuickAction.css} (100%) rename src/components/{VencordSettings/quickActions.tsx => settings/QuickAction.tsx} (97%) rename src/components/{VencordSettings/specialCard.css => settings/SpecialCard.css} (100%) rename src/components/{VencordSettings => settings}/SpecialCard.tsx (99%) rename src/components/{ => settings}/Switch.css (100%) rename src/components/{ => settings}/Switch.tsx (100%) create mode 100644 src/components/settings/index.ts rename src/components/{VencordSettings/shared.tsx => settings/tabs/BaseTab.tsx} (96%) create mode 100644 src/components/settings/tabs/index.ts create mode 100644 src/components/settings/tabs/patchHelper/FullPatchInput.tsx create mode 100644 src/components/settings/tabs/patchHelper/PatchPreview.tsx create mode 100644 src/components/settings/tabs/patchHelper/ReplacementInput.tsx create mode 100644 src/components/settings/tabs/patchHelper/index.tsx rename src/components/{PluginSettings/contributorModal.css => settings/tabs/plugins/ContributorModal.css} (100%) rename src/components/{PluginSettings => settings/tabs/plugins}/ContributorModal.tsx (98%) rename src/components/{PluginSettings => settings/tabs/plugins}/LinkIconButton.css (100%) rename src/components/{PluginSettings => settings/tabs/plugins}/LinkIconButton.tsx (95%) create mode 100644 src/components/settings/tabs/plugins/PluginCard.tsx rename src/components/{PluginSettings => settings/tabs/plugins}/PluginModal.css (100%) rename src/components/{PluginSettings => settings/tabs/plugins}/PluginModal.tsx (55%) rename src/components/{PluginSettings/components/SettingBooleanComponent.tsx => settings/tabs/plugins/components/BooleanSetting.tsx} (72%) create mode 100644 src/components/settings/tabs/plugins/components/Common.tsx rename src/components/{PluginSettings/components/SettingCustomComponent.tsx => settings/tabs/plugins/components/ComponentSetting.tsx} (76%) rename src/components/{PluginSettings/components/SettingNumericComponent.tsx => settings/tabs/plugins/components/NumberSetting.tsx} (59%) rename src/components/{PluginSettings/components/SettingSelectComponent.tsx => settings/tabs/plugins/components/SelectSetting.tsx} (56%) rename src/components/{PluginSettings/components/SettingSliderComponent.tsx => settings/tabs/plugins/components/SliderSetting.tsx} (58%) rename src/components/{PluginSettings/components/SettingTextComponent.tsx => settings/tabs/plugins/components/TextSetting.tsx} (52%) create mode 100644 src/components/settings/tabs/plugins/components/index.ts rename src/components/{PluginSettings => settings/tabs/plugins}/index.tsx (57%) rename src/components/{PluginSettings => settings/tabs/plugins}/styles.css (98%) rename src/components/{VencordSettings/settingsStyles.css => settings/tabs/styles.css} (100%) rename src/components/{VencordSettings => settings/tabs/sync}/BackupAndRestoreTab.tsx (93%) rename src/components/{VencordSettings => settings/tabs/sync}/CloudTab.tsx (99%) create mode 100644 src/components/settings/tabs/themes/CspErrorCard.tsx create mode 100644 src/components/settings/tabs/themes/LocalThemesTab.tsx create mode 100644 src/components/settings/tabs/themes/OnlineThemesTab.tsx create mode 100644 src/components/settings/tabs/themes/ThemeCard.tsx create mode 100644 src/components/settings/tabs/themes/index.tsx rename src/components/{VencordSettings/themesStyles.css => settings/tabs/themes/styles.css} (100%) create mode 100644 src/components/settings/tabs/updater/Components.tsx create mode 100644 src/components/settings/tabs/updater/index.tsx create mode 100644 src/components/settings/tabs/updater/runWithDispatch.tsx create mode 100644 src/components/settings/tabs/vencord/DonateButton.tsx create mode 100644 src/components/settings/tabs/vencord/MacVibrancySettings.tsx rename src/components/{VencordSettings => settings/tabs/vencord}/NotificationSettings.tsx (86%) create mode 100644 src/components/settings/tabs/vencord/index.tsx diff --git a/.gitignore b/.gitignore index 9f877c05..e45af171 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,4 @@ lerna-debug.log* src/userplugins ExtensionCache/ -settings/ +/settings diff --git a/packages/discord-types/src/components.d.ts b/packages/discord-types/src/components.d.ts index 8b2fd1cf..eff9d73a 100644 --- a/packages/discord-types/src/components.d.ts +++ b/packages/discord-types/src/components.d.ts @@ -244,8 +244,10 @@ export type TextInput = ComponentType; }; +// FIXME: this is wrong, it's not actually just HTMLTextAreaElement export type TextArea = ComponentType, "onChange"> & { onChange(v: string): void; + inputRef?: Ref; }>; interface SelectOption { diff --git a/src/Vencord.ts b/src/Vencord.ts index f18c7347..00f29347 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -16,6 +16,9 @@ * along with this program. If not, see . */ +// DO NOT REMOVE UNLESS YOU WISH TO FACE THE WRATH OF THE CIRCULAR DEPENDENCY DEMON!!!!!!! +import "~plugins"; + export * as Api from "./api"; export * as Components from "./components"; export * as Plugins from "./plugins"; @@ -29,7 +32,8 @@ export { PlainSettings, Settings }; import "./utils/quickCss"; import "./webpack/patchWebpack"; -import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab"; +import { openUpdaterModal } from "@components/settings/tabs/updater"; +import { IS_WINDOWS } from "@utils/constants"; import { StartAt } from "@utils/types"; import { get as dsGet } from "./api/DataStore"; @@ -161,7 +165,7 @@ init(); document.addEventListener("DOMContentLoaded", () => { startAllPlugins(StartAt.DOMContentLoaded); - if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && navigator.platform.toLowerCase().startsWith("win")) { + if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && IS_WINDOWS) { document.head.append(Object.assign(document.createElement("style"), { id: "vencord-native-titlebar-style", textContent: "[class*=titleBar]{display: none!important}" diff --git a/src/api/Badges.ts b/src/api/Badges.ts index ee2f3a30..f79ca099 100644 --- a/src/api/Badges.ts +++ b/src/api/Badges.ts @@ -17,10 +17,9 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; +import BadgeAPIPlugin from "plugins/_api/badges"; import { ComponentType, HTMLProps } from "react"; -import Plugins from "~plugins"; - export const enum BadgePosition { START, END @@ -90,7 +89,7 @@ export function _getBadges(args: BadgeUserArgs) { : badges.push(...b); } } - const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId); + const donorBadges = BadgeAPIPlugin.getDonorBadges(args.userId); if (donorBadges) badges.unshift(...donorBadges); return badges; diff --git a/src/api/MessageUpdater.ts b/src/api/MessageUpdater.ts index 7c4b3d5c..afbb04f0 100644 --- a/src/api/MessageUpdater.ts +++ b/src/api/MessageUpdater.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { FluxStore, Message } from "@vencord/discord-types"; +import { Message } from "@vencord/discord-types"; import { MessageCache, MessageStore } from "@webpack/common"; /** @@ -24,5 +24,5 @@ export function updateMessage(channelId: string, messageId: string, fields?: Par }); MessageCache.commit(newChannelMessageCache); - (MessageStore as unknown as FluxStore).emitChange(); + MessageStore.emitChange(); } diff --git a/src/api/Notifications/notificationLog.tsx b/src/api/Notifications/notificationLog.tsx index 5df31d4c..a943db07 100644 --- a/src/api/Notifications/notificationLog.tsx +++ b/src/api/Notifications/notificationLog.tsx @@ -20,7 +20,7 @@ import * as DataStore from "@api/DataStore"; import { Settings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import { Flex } from "@components/Flex"; -import { openNotificationSettingsModal } from "@components/VencordSettings/NotificationSettings"; +import { openNotificationSettingsModal } from "@components/settings/tabs/vencord/NotificationSettings"; import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index 4c5b0ca0..9862c1b4 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -27,7 +27,6 @@ interface BaseIconProps extends IconProps { } type IconProps = JSX.IntrinsicElements["svg"]; -type ImageProps = JSX.IntrinsicElements["img"]; function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren) { return ( diff --git a/src/components/PluginSettings/components/index.ts b/src/components/PluginSettings/components/index.ts deleted file mode 100644 index c38f209b..00000000 --- a/src/components/PluginSettings/components/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 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 { DefinedSettings, PluginOptionBase } from "@utils/types"; - -interface ISettingElementPropsBase { - option: T; - onChange(newValue: any): void; - pluginSettings: { - [setting: string]: any; - enabled: boolean; - }; - id: string; - onError(hasError: boolean): void; - definedSettings?: DefinedSettings; -} - -export type ISettingElementProps = ISettingElementPropsBase; -export type ISettingCustomElementProps> = ISettingElementPropsBase; - -export * from "../../Badge"; -export * from "./SettingBooleanComponent"; -export * from "./SettingCustomComponent"; -export * from "./SettingNumericComponent"; -export * from "./SettingSelectComponent"; -export * from "./SettingSliderComponent"; -export * from "./SettingTextComponent"; - diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx deleted file mode 100644 index 83364d19..00000000 --- a/src/components/VencordSettings/PatchHelperTab.tsx +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 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 { CodeBlock } from "@components/CodeBlock"; -import { debounce } from "@shared/debounce"; -import { copyToClipboard } from "@utils/clipboard"; -import { Margins } from "@utils/margins"; -import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches"; -import { makeCodeblock } from "@utils/text"; -import { Patch, ReplaceFn } from "@utils/types"; -import { search } from "@webpack"; -import { Button, Forms, Parser, React, Switch, TextArea, TextInput } from "@webpack/common"; - -import { SettingsTab, wrapTab } from "./shared"; - -// Do not include diff in non dev builds (side effects import) -if (IS_DEV) { - var differ = require("diff") as typeof import("diff"); -} - -const findCandidates = debounce(function ({ find, setModule, setError }) { - const candidates = search(find); - const keys = Object.keys(candidates); - const len = keys.length; - if (len === 0) - setError("No match. Perhaps that module is lazy loaded?"); - else if (len !== 1) - setError("Multiple matches. Please refine your filter"); - else - setModule([keys[0], candidates[keys[0]]]); -}); - -interface ReplacementComponentProps { - module: [id: number, factory: Function]; - match: string; - replacement: string | ReplaceFn; - setReplacementError(error: any): void; -} - -function ReplacementComponent({ module, match, replacement, setReplacementError }: ReplacementComponentProps) { - const [id, fact] = module; - const [compileResult, setCompileResult] = React.useState<[boolean, string]>(); - - const [patchedCode, matchResult, diff] = React.useMemo(() => { - const src: string = fact.toString().replaceAll("\n", ""); - - try { - new RegExp(match); - } catch (e) { - return ["", [], []]; - } - const canonicalMatch = canonicalizeMatch(new RegExp(match)); - try { - const canonicalReplace = canonicalizeReplace(replacement, 'Vencord.Plugins.plugins["YourPlugin"]'); - var patched = src.replace(canonicalMatch, canonicalReplace as string); - setReplacementError(void 0); - } catch (e) { - setReplacementError((e as Error).message); - return ["", [], []]; - } - const m = src.match(canonicalMatch); - return [patched, m, makeDiff(src, patched, m)]; - }, [id, match, replacement]); - - function makeDiff(original: string, patched: string, match: RegExpMatchArray | null) { - if (!match || original === patched) return null; - - const changeSize = patched.length - original.length; - - // Use 200 surrounding characters of context - const start = Math.max(0, match.index! - 200); - const end = Math.min(original.length, match.index! + match[0].length + 200); - // (changeSize may be negative) - const endPatched = end + changeSize; - - const context = original.slice(start, end); - const patchedContext = patched.slice(start, endPatched); - - return differ.diffWordsWithSpace(context, patchedContext); - } - - function renderMatch() { - if (!matchResult) - return Regex doesn't match!; - - const fullMatch = matchResult[0] ? makeCodeblock(matchResult[0], "js") : ""; - const groups = matchResult.length > 1 - ? makeCodeblock(matchResult.slice(1).map((g, i) => `Group ${i + 1}: ${g}`).join("\n"), "yml") - : ""; - - return ( - <> -
{Parser.parse(fullMatch)}
-
{Parser.parse(groups)}
- - ); - } - - function renderDiff() { - return diff?.map((p, idx) => { - const color = p.added ? "lime" : p.removed ? "red" : "grey"; - return
{p.value}
; - }); - } - - return ( - <> - Module {id} - - {!!matchResult?.[0]?.length && ( - <> - Match - {renderMatch()} - ) - } - - {!!diff?.length && ( - <> - Diff - {renderDiff()} - - )} - - {!!diff?.length && ( - - )} - - {compileResult && - - {compileResult[1]} - - } - - ); -} - -function ReplacementInput({ replacement, setReplacement, replacementError }) { - const [isFunc, setIsFunc] = React.useState(false); - const [error, setError] = React.useState(); - - function onChange(v: string) { - setError(void 0); - - if (isFunc) { - try { - const func = (0, eval)(v); - if (typeof func === "function") - setReplacement(() => func); - else - setError("Replacement must be a function"); - } catch (e) { - setReplacement(v); - setError((e as Error).message); - } - } else { - setReplacement(v); - } - } - - React.useEffect( - () => void (isFunc ? onChange(replacement) : setError(void 0)), - [isFunc] - ); - - return ( - <> - {/* FormTitle adds a class if className is not set, so we set it to an empty string to prevent that */} - replacement - - {!isFunc && ( -
- Cheat Sheet - {Object.entries({ - "\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)", - "$$": "Insert a $", - "$&": "Insert the entire match", - "$`\u200b": "Insert the substring before the match", - "$'": "Insert the substring after the match", - "$n": "Insert the nth capturing group ($1, $2...)", - "$self": "Insert the plugin instance", - }).map(([placeholder, desc]) => ( - - {Parser.parse("`" + placeholder + "`")}: {desc} - - ))} -
- )} - - - Treat as Function - - - ); -} - -interface FullPatchInputProps { - setFind(v: string): void; - setParsedFind(v: string | RegExp): void; - setMatch(v: string): void; - setReplacement(v: string | ReplaceFn): void; -} - -function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: FullPatchInputProps) { - const [fullPatch, setFullPatch] = React.useState(""); - const [fullPatchError, setFullPatchError] = React.useState(""); - - function update() { - if (fullPatch === "") { - setFullPatchError(""); - - setFind(""); - setParsedFind(""); - setMatch(""); - setReplacement(""); - return; - } - - try { - const parsed = (0, eval)(`([${fullPatch}][0])`) as Patch; - - if (!parsed.find) throw new Error("No 'find' field"); - if (!parsed.replacement) throw new Error("No 'replacement' field"); - - if (parsed.replacement instanceof Array) { - if (parsed.replacement.length === 0) throw new Error("Invalid replacement"); - - parsed.replacement = { - match: parsed.replacement[0].match, - replace: parsed.replacement[0].replace - }; - } - - if (!parsed.replacement.match) throw new Error("No 'replacement.match' field"); - if (!parsed.replacement.replace) throw new Error("No 'replacement.replace' field"); - - setFind(parsed.find instanceof RegExp ? parsed.find.toString() : parsed.find); - setParsedFind(parsed.find); - setMatch(parsed.replacement.match instanceof RegExp ? parsed.replacement.match.source : parsed.replacement.match); - setReplacement(parsed.replacement.replace); - setFullPatchError(""); - } catch (e) { - setFullPatchError((e as Error).message); - } - } - - return <> - Paste your full JSON patch here to fill out the fields -