Add in client updater, Notices API

This commit is contained in:
Vendicated
2022-10-01 00:42:50 +02:00
parent 9aaa47ea4e
commit 8161a07dba
20 changed files with 525 additions and 48 deletions

View File

@ -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>
);
}
}

View File

@ -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;

19
src/components/Link.tsx Normal file
View File

@ -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>
);
}

View File

@ -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}
@ -56,6 +75,13 @@ 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}
@ -63,8 +89,13 @@ export default ErrorBoundary.wrap(function Settings(props) {
>
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;

128
src/components/Updater.tsx Normal file
View File

@ -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>
</>
);
}