diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/ErrorBoundary.tsx | 8 | ||||
-rw-r--r-- | src/components/Flex.tsx | 1 | ||||
-rw-r--r-- | src/components/Link.tsx | 19 | ||||
-rw-r--r-- | src/components/Settings.tsx | 43 | ||||
-rw-r--r-- | src/components/Updater.tsx | 128 |
5 files changed, 189 insertions, 10 deletions
diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 2b754b2..5946cb1 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -1,5 +1,5 @@ import Logger from "../utils/logger"; -import { React } from "../webpack/common"; +import { Card, React } from "../webpack/common"; interface Props { fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; }>>; @@ -16,7 +16,7 @@ export default class ErrorBoundary extends React.Component<React.PropsWithChildr static wrap<T = any>(Component: React.ComponentType<T>): (props: T) => React.ReactElement { return (props) => ( <ErrorBoundary> - <Component {...props} /> + <Component {...props as any/* I hate react typings ??? */} /> </ErrorBoundary> ); } @@ -49,7 +49,7 @@ export default class ErrorBoundary extends React.Component<React.PropsWithChildr />; return ( - <div style={{ + <Card style={{ overflow: "hidden", padding: "2em", backgroundColor: color + "30", @@ -65,7 +65,7 @@ export default class ErrorBoundary extends React.Component<React.PropsWithChildr <pre>{this.state.error} </pre> </code> - </div> + </Card> ); } } diff --git a/src/components/Flex.tsx b/src/components/Flex.tsx index c369767..881c7c2 100644 --- a/src/components/Flex.tsx +++ b/src/components/Flex.tsx @@ -4,6 +4,7 @@ import type { React } from '../webpack/common'; export function Flex(props: React.PropsWithChildren<{ flexDirection?: React.CSSProperties["flexDirection"]; style?: React.CSSProperties; + className?: string; }>) { props.style ??= {}; props.style.flexDirection ||= props.flexDirection; diff --git a/src/components/Link.tsx b/src/components/Link.tsx new file mode 100644 index 0000000..ef342d1 --- /dev/null +++ b/src/components/Link.tsx @@ -0,0 +1,19 @@ +import { React } from "../webpack/common"; + +interface Props { + href: string; + disabled?: boolean; + style?: React.CSSProperties; +} + +export function Link(props: React.PropsWithChildren<Props>) { + if (props.disabled) { + props.style ??= {}; + props.style.pointerEvents = "none"; + } + return ( + <a href={props.href} target="_blank" style={props.style}> + {props.children} + </a> + ); +} diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 1950d7a..dd23b73 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -1,16 +1,19 @@ -import { humanFriendlyJoin, useAwaiter } from "../utils/misc"; +import { classes, humanFriendlyJoin, lazy, useAwaiter } from "../utils/misc"; import Plugins from 'plugins'; import { useSettings } from "../api/settings"; import IpcEvents from "../utils/IpcEvents"; -import { Button, Switch, Forms, React } from "../webpack/common"; +import { Button, Switch, Forms, React, Margins } from "../webpack/common"; import ErrorBoundary from "./ErrorBoundary"; import { startPlugin } from "../plugins"; import { stopPlugin } from '../plugins/index'; import { Flex } from './Flex'; +import { isOutdated } from "../utils/updater"; +import { Updater } from "./Updater"; export default ErrorBoundary.wrap(function Settings(props) { const [settingsDir, , settingsDirPending] = useAwaiter(() => VencordNative.ipc.invoke<string>(IpcEvents.GET_SETTINGS_DIR), "Loading..."); + const [outdated, setOutdated] = React.useState(isOutdated); const settings = useSettings(); const depMap = React.useMemo(() => { @@ -31,8 +34,24 @@ export default ErrorBoundary.wrap(function Settings(props) { return ( <Forms.FormSection tag="h1" title="Vencord"> - <Forms.FormText>SettingsDir: {settingsDir}</Forms.FormText> - <Flex style={{ marginTop: "8px", marginBottom: "8px" }}> + {outdated && ( + <> + <Forms.FormTitle tag="h5">Updater</Forms.FormTitle> + <Updater setIsOutdated={setOutdated} /> + </> + )} + + <Forms.FormDivider /> + + <Forms.FormTitle tag="h5" className={outdated ? `${Margins.marginTop20} ${Margins.marginBottom8}` : ""}> + Settings + </Forms.FormTitle> + + <Forms.FormText> + SettingsDir: {settingsDir} + </Forms.FormText> + + <Flex className={classes(Margins.marginBottom20)}> <Button onClick={() => VencordNative.ipc.invoke(IpcEvents.OPEN_PATH, settingsDir)} size={Button.Sizes.SMALL} @@ -48,7 +67,7 @@ export default ErrorBoundary.wrap(function Settings(props) { Open QuickCSS File </Button> </Flex> - <Forms.FormTitle tag="h5">Settings</Forms.FormTitle> + <Switch value={settings.useQuickCss} onChange={v => settings.useQuickCss = v} @@ -57,14 +76,26 @@ export default ErrorBoundary.wrap(function Settings(props) { Use QuickCss </Switch> <Switch + value={settings.notifyAboutUpdates} + onChange={v => settings.notifyAboutUpdates = v} + note="Shows a Toast on StartUp" + > + Get notified about new Updates + </Switch> + <Switch value={settings.unsafeRequire} onChange={v => settings.unsafeRequire = v} note="Enables VencordNative.require. Useful for testing, very bad for security. Leave this off unless you need it." > Enable Unsafe Require </Switch> + <Forms.FormDivider /> - <Forms.FormTitle tag="h5">Plugins</Forms.FormTitle> + + <Forms.FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}> + Plugins + </Forms.FormTitle> + {sortedPlugins.map(p => { const enabledDependants = depMap[p.name]?.filter(d => settings.plugins[d].enabled); const dependency = enabledDependants?.length; diff --git a/src/components/Updater.tsx b/src/components/Updater.tsx new file mode 100644 index 0000000..e7b6d54 --- /dev/null +++ b/src/components/Updater.tsx @@ -0,0 +1,128 @@ +import gitHash from "git-hash"; +import { changes, checkForUpdates, getRepo, rebuild, update, UpdateLogger } from "../utils/updater"; +import { React, Forms, Button, Margins, Alerts, Card, Parser } from '../webpack/common'; +import { Flex } from "./Flex"; +import { useAwaiter } from '../utils/misc'; +import { Link } from "./Link"; + +interface Props { + setIsOutdated(b: boolean): void; +} + +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: err.split("\n").map(line => <div>{Parser.parse(line)}</div>) + }); + } + finally { + dispatcher(false); + } + }; +}; + +export function Updater(p: Props) { + const [repo, err, repoPending] = useAwaiter(getRepo, "Loading..."); + const [isChecking, setIsChecking] = React.useState(false); + const [isUpdating, setIsUpdating] = React.useState(false); + const [updates, setUpdates] = React.useState(changes); + + React.useEffect(() => { + if (err) + UpdateLogger.error("Failed to retrieve repo", err); + }, [err]); + + return ( + <> + <Forms.FormText>Repo: {repoPending ? repo : err ? "Failed to retrieve - check console" : ( + <Link href={repo}> + {repo.split("/").slice(-2).join("/")} + </Link> + )} ({gitHash})</Forms.FormText> + + <Forms.FormText className={Margins.marginBottom8}> + There are {updates.length} Updates + </Forms.FormText> + + <Card style={{ padding: ".5em" }}> + {updates.map(({ hash, author, message }) => ( + <div> + <Link href={`${repo}/commit/${hash}`} disabled={repoPending}> + <code>{hash}</code> + </Link> + <span style={{ + marginLeft: "0.5em", + color: "var(--text-normal)" + }}>{message} - {author}</span> + </div> + ))} + </Card> + + <Flex className={`${Margins.marginBottom8} ${Margins.marginTop8}`}> + <Button + size={Button.Sizes.SMALL} + disabled={isUpdating || isChecking} + onClick={withDispatcher(setIsUpdating, async () => { + if (await update()) { + p.setIsOutdated(false); + const needFullRestart = await rebuild(); + 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() { + if (needFullRestart) + window.DiscordNative.app.relaunch(); + else + location.reload(); + r(); + }, + onCancel: r + }); + }); + } + })} + > + Update + </Button> + <Button + size={Button.Sizes.SMALL} + disabled={isUpdating || isChecking} + onClick={withDispatcher(setIsChecking, async () => { + const res = await checkForUpdates(); + if (res) { + setUpdates(changes); + } else { + p.setIsOutdated(false); + } + })} + > + Refresh + </Button> + </Flex> + </> + ); +} |