diff options
Diffstat (limited to 'src/components/VencordSettings/UpdaterTab.tsx')
-rw-r--r-- | src/components/VencordSettings/UpdaterTab.tsx | 252 |
1 files changed, 252 insertions, 0 deletions
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 <https://www.gnu.org/licenses/>. +*/ + +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<React.SetStateAction<boolean>>, 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: ( + <ErrorCard> + {err.split("\n").map(line => <div>{Parser.parse(line)}</div>)} + </ErrorCard> + ) + }); + } + finally { + dispatcher(false); + } + }; +} + +interface CommonProps { + repo: string; + repoPending: boolean; +} + +function HashLink({ repo, hash, disabled = false }: { repo: string, hash: string, disabled?: boolean; }) { + return <Link href={`${repo}/commit/${hash}`} disabled={disabled}> + {hash} + </Link>; +} + +function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) { + return ( + <Card style={{ padding: ".5em" }}> + {updates.map(({ hash, author, message }) => ( + <div> + <code><HashLink {...{ repo, hash }} disabled={repoPending} /></code> + <span style={{ + marginLeft: "0.5em", + color: "var(--text-normal)" + }}>{message} - {author}</span> + </div> + ))} + </Card> + ); +} + +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 ? ( + <> + <Forms.FormText>Failed to check updates. Check the console for more info</Forms.FormText> + <ErrorCard style={{ padding: "1em" }}> + <p>{updateError.stderr || updateError.stdout || "An unknown error occurred"}</p> + </ErrorCard> + </> + ) : ( + <Forms.FormText className={Margins.bottom8}> + {isOutdated ? `There are ${updates.length} Updates` : "Up to Date!"} + </Forms.FormText> + )} + + {isOutdated && <Changes updates={updates} {...props} />} + + <Flex className={classes(Margins.bottom8, Margins.top8)}> + {isOutdated && <Button + size={Button.Sizes.SMALL} + disabled={isUpdating || isChecking} + onClick={withDispatcher(setIsUpdating, async () => { + if (await update()) { + setUpdates([]); + await new Promise<void>(r => { + Alerts.show({ + title: "Update Success!", + body: "Successfully updated. Restart now to apply the changes?", + confirmText: "Restart", + cancelText: "Not now!", + onConfirm() { + relaunch(); + r(); + }, + onCancel: r + }); + }); + } + })} + > + Update Now + </Button>} + <Button + size={Button.Sizes.SMALL} + disabled={isUpdating || isChecking} + onClick={withDispatcher(setIsChecking, async () => { + const outdated = await checkForUpdates(); + if (outdated) { + setUpdates(changes); + } else { + setUpdates([]); + Toasts.show({ + message: "No updates found!", + id: Toasts.genId(), + type: Toasts.Type.MESSAGE, + options: { + position: Toasts.Position.BOTTOM + } + }); + } + })} + > + Check for Updates + </Button> + </Flex> + </> + ); +} + +function Newer(props: CommonProps) { + return ( + <> + <Forms.FormText className={Margins.bottom8}> + Your local copy has more recent commits. Please stash or reset them. + </Forms.FormText> + <Changes {...props} updates={changes} /> + </> + ); +} + +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 ( + <SettingsTab title="Vencord Updater"> + <Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle> + <Switch + value={settings.notifyAboutUpdates} + onChange={(v: boolean) => settings.notifyAboutUpdates = v} + note="Shows a notification on startup" + disabled={settings.autoUpdate} + > + Get notified about new updates + </Switch> + <Switch + value={settings.autoUpdate} + onChange={(v: boolean) => settings.autoUpdate = v} + note="Automatically update Vencord without confirmation prompt" + > + Automatically update + </Switch> + <Switch + value={settings.autoUpdateNotification} + onChange={(v: boolean) => settings.autoUpdateNotification = v} + note="Shows a notification when Vencord automatically updates" + disabled={!settings.autoUpdate} + > + Get notified when an automatic update completes + </Switch> + + <Forms.FormTitle tag="h5">Repo</Forms.FormTitle> + + <Forms.FormText className="vc-text-selectable"> + {repoPending + ? repo + : err + ? "Failed to retrieve - check console" + : ( + <Link href={repo}> + {repo.split("/").slice(-2).join("/")} + </Link> + ) + } + {" "}(<HashLink hash={gitHash} repo={repo} disabled={repoPending} />) + </Forms.FormText> + + <Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} /> + + <Forms.FormTitle tag="h5">Updates</Forms.FormTitle> + + {isNewer ? <Newer {...commonProps} /> : <Updatable {...commonProps} />} + </SettingsTab> + ); +} + +export default IS_WEB ? null : wrapTab(Updater, "Updater"); |