From 5c5b009c4180b73603a9c3a6c6663f889a2e2062 Mon Sep 17 00:00:00 2001 From: V Date: Fri, 12 May 2023 01:40:43 +0200 Subject: Settings: Fix resetting scroll/search when getting a ping (#1106) --- .../VencordSettings/BackupAndRestoreTab.tsx | 67 +++++ .../VencordSettings/BackupRestoreTab.tsx | 66 ----- src/components/VencordSettings/CloudTab.tsx | 9 +- src/components/VencordSettings/PatchHelperTab.tsx | 310 +++++++++++++++++++++ src/components/VencordSettings/PluginsTab.tsx | 5 +- src/components/VencordSettings/ThemesTab.tsx | 15 +- src/components/VencordSettings/Updater.tsx | 256 ----------------- src/components/VencordSettings/UpdaterTab.tsx | 252 +++++++++++++++++ src/components/VencordSettings/VencordTab.tsx | 9 +- src/components/VencordSettings/index.tsx | 96 ------- src/components/VencordSettings/settingsStyles.css | 2 - src/components/VencordSettings/shared.tsx | 51 ++++ 12 files changed, 702 insertions(+), 436 deletions(-) create mode 100644 src/components/VencordSettings/BackupAndRestoreTab.tsx delete mode 100644 src/components/VencordSettings/BackupRestoreTab.tsx create mode 100644 src/components/VencordSettings/PatchHelperTab.tsx delete mode 100644 src/components/VencordSettings/Updater.tsx create mode 100644 src/components/VencordSettings/UpdaterTab.tsx delete mode 100644 src/components/VencordSettings/index.tsx create mode 100644 src/components/VencordSettings/shared.tsx (limited to 'src/components/VencordSettings') diff --git a/src/components/VencordSettings/BackupAndRestoreTab.tsx b/src/components/VencordSettings/BackupAndRestoreTab.tsx new file mode 100644 index 0000000..a9a1c9f --- /dev/null +++ b/src/components/VencordSettings/BackupAndRestoreTab.tsx @@ -0,0 +1,67 @@ +/* + * 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 { Flex } from "@components/Flex"; +import { Margins } from "@utils/margins"; +import { classes } from "@utils/misc"; +import { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync"; +import { Button, Card, Text } from "@webpack/common"; + +import { SettingsTab, wrapTab } from "./shared"; + +function BackupRestoreTab() { + return ( + + + + Warning + Importing a settings file will overwrite your current settings. + + + + You can import and export your Vencord settings as a JSON file. + This allows you to easily transfer your settings to another device, + or recover your settings after reinstalling Vencord or Discord. + + + Settings Export contains: +
    +
  • — Custom QuickCSS
  • +
  • — Theme Links
  • +
  • — Plugin Settings
  • +
+
+ + + + +
+ ); +} + +export default wrapTab(BackupRestoreTab, "Backup & Restore"); diff --git a/src/components/VencordSettings/BackupRestoreTab.tsx b/src/components/VencordSettings/BackupRestoreTab.tsx deleted file mode 100644 index 1737470..0000000 --- a/src/components/VencordSettings/BackupRestoreTab.tsx +++ /dev/null @@ -1,66 +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 ErrorBoundary from "@components/ErrorBoundary"; -import { Flex } from "@components/Flex"; -import { Margins } from "@utils/margins"; -import { classes } from "@utils/misc"; -import { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync"; -import { Button, Card, Forms, Text } from "@webpack/common"; - -function BackupRestoreTab() { - return ( - - - - Warning - Importing a settings file will overwrite your current settings. - - - - You can import and export your Vencord settings as a JSON file. - This allows you to easily transfer your settings to another device, - or recover your settings after reinstalling Vencord or Discord. - - - Settings Export contains: -
    -
  • — Custom QuickCSS
  • -
  • — Theme Links
  • -
  • — Plugin Settings
  • -
-
- - - - -
- ); -} - -export default ErrorBoundary.wrap(BackupRestoreTab); diff --git a/src/components/VencordSettings/CloudTab.tsx b/src/components/VencordSettings/CloudTab.tsx index 5e48a72..77e5298 100644 --- a/src/components/VencordSettings/CloudTab.tsx +++ b/src/components/VencordSettings/CloudTab.tsx @@ -19,13 +19,14 @@ import { showNotification } from "@api/Notifications"; import { Settings, useSettings } from "@api/Settings"; import { CheckedTextInput } from "@components/CheckedTextInput"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Link } from "@components/Link"; import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud"; import { Margins } from "@utils/margins"; import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync"; import { Alerts, Button, Forms, Switch, Tooltip } from "@webpack/common"; +import { SettingsTab, wrapTab } from "./shared"; + function validateUrl(url: string) { try { new URL(url); @@ -114,7 +115,7 @@ function CloudTab() { const settings = useSettings(["cloud.authenticated", "cloud.url"]); return ( - <> + Vencord comes with a cloud integration that adds goodies like settings sync across devices. @@ -157,8 +158,8 @@ function CloudTab() { - + ); } -export default ErrorBoundary.wrap(CloudTab); +export default wrapTab(CloudTab, "Cloud"); diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx new file mode 100644 index 0000000..d5bd94c --- /dev/null +++ b/src/components/VencordSettings/PatchHelperTab.tsx @@ -0,0 +1,310 @@ +/* + * 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 { CheckedTextInput } from "@components/CheckedTextInput"; +import { debounce } from "@utils/debounce"; +import { Margins } from "@utils/margins"; +import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches"; +import { makeCodeblock } from "@utils/text"; +import { ReplaceFn } from "@utils/types"; +import { search } from "@webpack"; +import { Button, Clipboard, Forms, Parser, React, Switch, 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 | RegExp; + 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", ""); + const canonicalMatch = canonicalizeMatch(match); + try { + const canonicalReplace = canonicalizeReplace(replacement, "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 => { + 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 ( + <> + 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 + + + ); +} + +function PatchHelper() { + const [find, setFind] = React.useState(""); + const [match, setMatch] = React.useState(""); + const [replacement, setReplacement] = React.useState(""); + + const [replacementError, setReplacementError] = React.useState(); + + const [module, setModule] = React.useState<[number, Function]>(); + const [findError, setFindError] = React.useState(); + + const code = React.useMemo(() => { + return ` +{ + find: ${JSON.stringify(find)}, + replacement: { + match: /${match.replace(/(? + find + + + match + { + try { + return (new RegExp(v), true); + } catch (e) { + return (e as Error).message; + } + }} + /> + + + + + {module && ( + + )} + + {!!(find && match && replacement) && ( + <> + Code +
{Parser.parse(makeCodeblock(code, "ts"))}
+ + + )} + + ); +} + +export default IS_DEV ? wrapTab(PatchHelper, "PatchHelper") : null; diff --git a/src/components/VencordSettings/PluginsTab.tsx b/src/components/VencordSettings/PluginsTab.tsx index 04b5dc2..6a32095 100644 --- a/src/components/VencordSettings/PluginsTab.tsx +++ b/src/components/VencordSettings/PluginsTab.tsx @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import ErrorBoundary from "@components/ErrorBoundary"; import PluginSettings from "@components/PluginSettings"; -export default ErrorBoundary.wrap(PluginSettings); +import { wrapTab } from "./shared"; + +export default wrapTab(PluginSettings, "Plugins"); diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index 75fea34..79ddc50 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -17,13 +17,14 @@ */ import { useSettings } from "@api/Settings"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Link } from "@components/Link"; import { Margins } from "@utils/margins"; import { useAwaiter } from "@utils/react"; import { findLazy } from "@webpack"; import { Card, Forms, React, TextArea } from "@webpack/common"; +import { SettingsTab, wrapTab } from "./shared"; + const TextAreaProps = findLazy(m => typeof m.textarea === "string"); function Validator({ link }: { link: string; }) { @@ -74,8 +75,8 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) { ); } -export default ErrorBoundary.wrap(function () { - const settings = useSettings(); +function ThemesTab() { + const settings = useSettings(["themeLinks"]); const [themeText, setThemeText] = React.useState(settings.themeLinks.join("\n")); function onBlur() { @@ -89,7 +90,7 @@ export default ErrorBoundary.wrap(function () { } return ( - <> + Paste links to .theme.css files here One link per line @@ -124,6 +125,8 @@ export default ErrorBoundary.wrap(function () { onBlur={onBlur} /> - + ); -}); +} + +export default wrapTab(ThemesTab, "Themes"); diff --git a/src/components/VencordSettings/Updater.tsx b/src/components/VencordSettings/Updater.tsx deleted file mode 100644 index 9345d27..0000000 --- a/src/components/VencordSettings/Updater.tsx +++ /dev/null @@ -1,256 +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 { useSettings } from "@api/Settings"; -import ErrorBoundary from "@components/ErrorBoundary"; -import { ErrorCard } from "@components/ErrorCard"; -import { Flex } from "@components/Flex"; -import { handleComponentFailed } from "@components/handleComponentFailed"; -import { Link } from "@components/Link"; -import { Margins } from "@utils/margins"; -import { classes } from "@utils/misc"; -import { relaunch } from "@utils/native"; -import { onlyOnce } from "@utils/onlyOnce"; -import { useAwaiter } from "@utils/react"; -import { changes, checkForUpdates, getRepo, isNewer, update, updateError, UpdateLogger } from "@utils/updater"; -import { Alerts, Button, Card, Forms, Parser, React, Switch, Toasts } from "@webpack/common"; - -import gitHash from "~git-hash"; - -function withDispatcher(dispatcher: React.Dispatch>, action: () => any) { - return async () => { - dispatcher(true); - try { - await action(); - } catch (e: any) { - UpdateLogger.error("Failed to update", e); - if (!e) { - var err = "An unknown error occurred (error is undefined).\nPlease try again."; - } else if (e.code && e.cmd) { - const { code, path, cmd, stderr } = e; - - if (code === "ENOENT") - var err = `Command \`${path}\` not found.\nPlease install it and try again`; - else { - var err = `An error occured while running \`${cmd}\`:\n`; - err += stderr || `Code \`${code}\`. See the console for more info`; - } - - } else { - var err = "An unknown error occurred. See the console for more info."; - } - Alerts.show({ - title: "Oops!", - body: ( - - {err.split("\n").map(line =>
{Parser.parse(line)}
)} -
- ) - }); - } - finally { - dispatcher(false); - } - }; -} - -interface CommonProps { - repo: string; - repoPending: boolean; -} - -function HashLink({ repo, hash, disabled = false }: { repo: string, hash: string, disabled?: boolean; }) { - return - {hash} - ; -} - -function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) { - return ( - - {updates.map(({ hash, author, message }) => ( -
- - {message} - {author} -
- ))} -
- ); -} - -function Updatable(props: CommonProps) { - const [updates, setUpdates] = React.useState(changes); - const [isChecking, setIsChecking] = React.useState(false); - const [isUpdating, setIsUpdating] = React.useState(false); - - const isOutdated = (updates?.length ?? 0) > 0; - - return ( - <> - {!updates && updateError ? ( - <> - Failed to check updates. Check the console for more info - -

{updateError.stderr || updateError.stdout || "An unknown error occurred"}

-
- - ) : ( - - {isOutdated ? `There are ${updates.length} Updates` : "Up to Date!"} - - )} - - {isOutdated && } - - - {isOutdated && } - - - - ); -} - -function Newer(props: CommonProps) { - return ( - <> - - Your local copy has more recent commits. Please stash or reset them. - - - - ); -} - -function Updater() { - const settings = useSettings(["notifyAboutUpdates", "autoUpdate", "autoUpdateNotification"]); - - const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: "Loading..." }); - - React.useEffect(() => { - if (err) - UpdateLogger.error("Failed to retrieve repo", err); - }, [err]); - - const commonProps: CommonProps = { - repo, - repoPending - }; - - return ( - - Updater Settings - settings.notifyAboutUpdates = v} - note="Shows a notification on startup" - disabled={settings.autoUpdate} - > - Get notified about new updates - - settings.autoUpdate = v} - note="Automatically update Vencord without confirmation prompt" - > - Automatically update - - settings.autoUpdateNotification = v} - note="Shows a notification when Vencord automatically updates" - disabled={!settings.autoUpdate} - > - Get notified when an automatic update completes - - - Repo - - - {repoPending - ? repo - : err - ? "Failed to retrieve - check console" - : ( - - {repo.split("/").slice(-2).join("/")} - - ) - } - {" "}() - - - - - Updates - - {isNewer ? : } - - ); -} - -export default IS_WEB ? null : ErrorBoundary.wrap(Updater, { - message: "Failed to render the Updater. If this persists, try using the installer to reinstall!", - onError: onlyOnce(handleComponentFailed), -}); diff --git a/src/components/VencordSettings/UpdaterTab.tsx b/src/components/VencordSettings/UpdaterTab.tsx new file mode 100644 index 0000000..4d0b86c --- /dev/null +++ b/src/components/VencordSettings/UpdaterTab.tsx @@ -0,0 +1,252 @@ +/* + * 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 { useSettings } from "@api/Settings"; +import { ErrorCard } from "@components/ErrorCard"; +import { Flex } from "@components/Flex"; +import { Link } from "@components/Link"; +import { Margins } from "@utils/margins"; +import { classes } from "@utils/misc"; +import { relaunch } from "@utils/native"; +import { useAwaiter } from "@utils/react"; +import { changes, checkForUpdates, getRepo, isNewer, update, updateError, UpdateLogger } from "@utils/updater"; +import { Alerts, Button, Card, Forms, Parser, React, Switch, Toasts } from "@webpack/common"; + +import gitHash from "~git-hash"; + +import { SettingsTab, wrapTab } from "./shared"; + +function withDispatcher(dispatcher: React.Dispatch>, action: () => any) { + return async () => { + dispatcher(true); + try { + await action(); + } catch (e: any) { + UpdateLogger.error("Failed to update", e); + if (!e) { + var err = "An unknown error occurred (error is undefined).\nPlease try again."; + } else if (e.code && e.cmd) { + const { code, path, cmd, stderr } = e; + + if (code === "ENOENT") + var err = `Command \`${path}\` not found.\nPlease install it and try again`; + else { + var err = `An error occured while running \`${cmd}\`:\n`; + err += stderr || `Code \`${code}\`. See the console for more info`; + } + + } else { + var err = "An unknown error occurred. See the console for more info."; + } + Alerts.show({ + title: "Oops!", + body: ( + + {err.split("\n").map(line =>
{Parser.parse(line)}
)} +
+ ) + }); + } + finally { + dispatcher(false); + } + }; +} + +interface CommonProps { + repo: string; + repoPending: boolean; +} + +function HashLink({ repo, hash, disabled = false }: { repo: string, hash: string, disabled?: boolean; }) { + return + {hash} + ; +} + +function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) { + return ( + + {updates.map(({ hash, author, message }) => ( +
+ + {message} - {author} +
+ ))} +
+ ); +} + +function Updatable(props: CommonProps) { + const [updates, setUpdates] = React.useState(changes); + const [isChecking, setIsChecking] = React.useState(false); + const [isUpdating, setIsUpdating] = React.useState(false); + + const isOutdated = (updates?.length ?? 0) > 0; + + return ( + <> + {!updates && updateError ? ( + <> + Failed to check updates. Check the console for more info + +

{updateError.stderr || updateError.stdout || "An unknown error occurred"}

+
+ + ) : ( + + {isOutdated ? `There are ${updates.length} Updates` : "Up to Date!"} + + )} + + {isOutdated && } + + + {isOutdated && } + + + + ); +} + +function Newer(props: CommonProps) { + return ( + <> + + Your local copy has more recent commits. Please stash or reset them. + + + + ); +} + +function Updater() { + const settings = useSettings(["notifyAboutUpdates", "autoUpdate", "autoUpdateNotification"]); + + const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: "Loading..." }); + + React.useEffect(() => { + if (err) + UpdateLogger.error("Failed to retrieve repo", err); + }, [err]); + + const commonProps: CommonProps = { + repo, + repoPending + }; + + return ( + + Updater Settings + settings.notifyAboutUpdates = v} + note="Shows a notification on startup" + disabled={settings.autoUpdate} + > + Get notified about new updates + + settings.autoUpdate = v} + note="Automatically update Vencord without confirmation prompt" + > + Automatically update + + settings.autoUpdateNotification = v} + note="Shows a notification when Vencord automatically updates" + disabled={!settings.autoUpdate} + > + Get notified when an automatic update completes + + + Repo + + + {repoPending + ? repo + : err + ? "Failed to retrieve - check console" + : ( + + {repo.split("/").slice(-2).join("/")} + + ) + } + {" "}() + + + + + Updates + + {isNewer ? : } + + ); +} + +export default IS_WEB ? null : wrapTab(Updater, "Updater"); diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx index 8c71821..1502bfa 100644 --- a/src/components/VencordSettings/VencordTab.tsx +++ b/src/components/VencordSettings/VencordTab.tsx @@ -21,7 +21,6 @@ import { openNotificationLogModal } from "@api/Notifications/notificationLog"; import { Settings, useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import DonateButton from "@components/DonateButton"; -import ErrorBoundary from "@components/ErrorBoundary"; import { ErrorCard } from "@components/ErrorCard"; import { Margins } from "@utils/margins"; import { identity } from "@utils/misc"; @@ -29,6 +28,8 @@ import { relaunch, showItemInFolder } from "@utils/native"; import { useAwaiter } from "@utils/react"; import { Button, Card, Forms, React, Select, Slider, Switch } from "@webpack/common"; +import { SettingsTab, wrapTab } from "./shared"; + const cl = classNameFactory("vc-settings-"); const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png"; @@ -97,7 +98,7 @@ function VencordSettings() { ]; return ( - + @@ -153,7 +154,7 @@ function VencordSettings() { {typeof Notification !== "undefined" && } - + ); } @@ -263,4 +264,4 @@ function DonateCard({ image }: DonateCardProps) { ); } -export default ErrorBoundary.wrap(VencordSettings); +export default wrapTab(VencordSettings, "Vencord Settings"); diff --git a/src/components/VencordSettings/index.tsx b/src/components/VencordSettings/index.tsx deleted file mode 100644 index 6d65aa1..0000000 --- a/src/components/VencordSettings/index.tsx +++ /dev/null @@ -1,96 +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 "./settingsStyles.css"; - -import { classNameFactory } from "@api/Styles"; -import ErrorBoundary from "@components/ErrorBoundary"; -import { handleComponentFailed } from "@components/handleComponentFailed"; -import { isMobile } from "@utils/misc"; -import { onlyOnce } from "@utils/onlyOnce"; -import { Forms, SettingsRouter, TabBar, Text } from "@webpack/common"; - -import BackupRestoreTab from "./BackupRestoreTab"; -import CloudTab from "./CloudTab"; -import PluginsTab from "./PluginsTab"; -import ThemesTab from "./ThemesTab"; -import Updater from "./Updater"; -import VencordSettings from "./VencordTab"; - -const cl = classNameFactory("vc-settings-"); - -interface SettingsProps { - tab: string; -} - -interface SettingsTab { - name: string; - component?: React.ComponentType; -} - -const SettingsTabs: Record = { - VencordSettings: { name: "Vencord", component: () => }, - VencordPlugins: { name: "Plugins", component: () => }, - VencordThemes: { name: "Themes", component: () => }, - VencordUpdater: { name: "Updater" }, // Only show updater if IS_WEB is false - VencordCloud: { name: "Cloud", component: () => }, - VencordSettingsSync: { name: "Backup & Restore", component: () => } -}; - -if (!IS_WEB) SettingsTabs.VencordUpdater.component = () => Updater && ; - -function Settings(props: SettingsProps) { - const { tab = "VencordSettings" } = props; - - const CurrentTab = SettingsTabs[tab]?.component ?? null; - if (isMobile) { - return CurrentTab && ; - } - - return - Vencord Settings - - - {Object.entries(SettingsTabs).map(([key, { name, component }]) => { - if (!component) return null; - return - {name} - ; - })} - - - {CurrentTab && } - ; -} - -const onError = onlyOnce(handleComponentFailed); - -export default function (props: SettingsProps) { - return - - ; -} diff --git a/src/components/VencordSettings/settingsStyles.css b/src/components/VencordSettings/settingsStyles.css index 3652756..f7d75e6 100644 --- a/src/components/VencordSettings/settingsStyles.css +++ b/src/components/VencordSettings/settingsStyles.css @@ -29,14 +29,12 @@ .vc-settings-card { padding: 1em; margin-bottom: 1em; - margin-top: 1em; } .vc-backup-restore-card { background-color: var(--info-warning-background); border-color: var(--info-warning-foreground); color: var(--info-warning-text); - margin-top: 0; } .vc-settings-theme-links { diff --git a/src/components/VencordSettings/shared.tsx b/src/components/VencordSettings/shared.tsx new file mode 100644 index 0000000..0d3910d --- /dev/null +++ b/src/components/VencordSettings/shared.tsx @@ -0,0 +1,51 @@ +/* + * 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 . +*/ + +import "./settingsStyles.css"; + +import ErrorBoundary from "@components/ErrorBoundary"; +import { handleComponentFailed } from "@components/handleComponentFailed"; +import { Margins } from "@utils/margins"; +import { onlyOnce } from "@utils/onlyOnce"; +import { Forms, Text } from "@webpack/common"; +import type { ComponentType, PropsWithChildren } from "react"; + +export function SettingsTab({ title, children }: PropsWithChildren<{ title: string; }>) { + return ( + + + {title} + + + {children} + + ); +} + +const onError = onlyOnce(handleComponentFailed); + +export function wrapTab(component: ComponentType, tab: string) { + return ErrorBoundary.wrap(component, { + message: `Failed to render the ${tab} tab. If this issue persists, try using the installer to reinstall!`, + onError, + }); +} -- cgit