PatchHelper, a tool to help you write patches (#182)
This commit is contained in:
parent
0c25278c59
commit
04d6f341ee
10 changed files with 394 additions and 43 deletions
|
@ -37,6 +37,7 @@
|
||||||
"@types/diff": "^5.0.2",
|
"@types/diff": "^5.0.2",
|
||||||
"@types/node": "^18.7.13",
|
"@types/node": "^18.7.13",
|
||||||
"@types/react": "^18.0.17",
|
"@types/react": "^18.0.17",
|
||||||
|
"@types/react-dom": "^18.0.8",
|
||||||
"@types/yazl": "^2.4.2",
|
"@types/yazl": "^2.4.2",
|
||||||
"@typescript-eslint/parser": "^5.39.0",
|
"@typescript-eslint/parser": "^5.39.0",
|
||||||
"discord-types": "^1.3.26",
|
"discord-types": "^1.3.26",
|
||||||
|
|
|
@ -4,6 +4,7 @@ specifiers:
|
||||||
'@types/diff': ^5.0.2
|
'@types/diff': ^5.0.2
|
||||||
'@types/node': ^18.7.13
|
'@types/node': ^18.7.13
|
||||||
'@types/react': ^18.0.17
|
'@types/react': ^18.0.17
|
||||||
|
'@types/react-dom': ^18.0.8
|
||||||
'@types/yazl': ^2.4.2
|
'@types/yazl': ^2.4.2
|
||||||
'@typescript-eslint/parser': ^5.39.0
|
'@typescript-eslint/parser': ^5.39.0
|
||||||
console-menu: ^0.1.0
|
console-menu: ^0.1.0
|
||||||
|
@ -28,6 +29,7 @@ devDependencies:
|
||||||
'@types/diff': 5.0.2
|
'@types/diff': 5.0.2
|
||||||
'@types/node': 18.7.13
|
'@types/node': 18.7.13
|
||||||
'@types/react': 18.0.17
|
'@types/react': 18.0.17
|
||||||
|
'@types/react-dom': 18.0.8
|
||||||
'@types/yazl': 2.4.2
|
'@types/yazl': 2.4.2
|
||||||
'@typescript-eslint/parser': 5.39.0_ypn2ylkkyfa5i233caldtndbqa
|
'@typescript-eslint/parser': 5.39.0_ypn2ylkkyfa5i233caldtndbqa
|
||||||
discord-types: 1.3.26
|
discord-types: 1.3.26
|
||||||
|
@ -129,6 +131,12 @@ packages:
|
||||||
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
|
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/react-dom/18.0.8:
|
||||||
|
resolution: {integrity: sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw==}
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.0.17
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/react/17.0.2:
|
/@types/react/17.0.2:
|
||||||
resolution: {integrity: sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==}
|
resolution: {integrity: sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
68
src/components/CheckedTextInput.tsx
Normal file
68
src/components/CheckedTextInput.tsx
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* 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 { React, TextInput } from "../webpack/common";
|
||||||
|
|
||||||
|
// TODO: Refactor settings to use this as well
|
||||||
|
interface TextInputProps {
|
||||||
|
/**
|
||||||
|
* WARNING: Changing this between renders will have no effect!
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
/**
|
||||||
|
* This will only be called if the new value passed validate()
|
||||||
|
*/
|
||||||
|
onChange(newValue: string): void;
|
||||||
|
/**
|
||||||
|
* Optionally validate the user input
|
||||||
|
* Return true if the input is valid
|
||||||
|
* Otherwise, return a string containing the reason for this input being invalid
|
||||||
|
*/
|
||||||
|
validate(v: string): true | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A very simple wrapper around Discord's TextInput that validates input and shows
|
||||||
|
* the user an error message and only calls your onChange when the input is valid
|
||||||
|
*/
|
||||||
|
export function CheckedTextInput({ value: initialValue, onChange, validate }: TextInputProps) {
|
||||||
|
const [value, setValue] = React.useState(initialValue);
|
||||||
|
const [error, setError] = React.useState<string>();
|
||||||
|
|
||||||
|
function handleChange(v: string) {
|
||||||
|
setValue(v);
|
||||||
|
const res = validate(v);
|
||||||
|
if (res === true) {
|
||||||
|
setError(void 0);
|
||||||
|
onChange(v);
|
||||||
|
} else {
|
||||||
|
setError(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
279
src/components/PatchHelper.tsx
Normal file
279
src/components/PatchHelper.tsx
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
/*
|
||||||
|
* 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 { makeCodeblock } from "../utils";
|
||||||
|
import { debounce } from "../utils/debounce";
|
||||||
|
import { Button, Clipboard, Forms, Margins, Parser, React, Switch, TextInput } from "../webpack/common";
|
||||||
|
import { search } from "../webpack/webpack";
|
||||||
|
import { CheckedTextInput } from "./CheckedTextInput";
|
||||||
|
import ErrorBoundary from "./ErrorBoundary";
|
||||||
|
|
||||||
|
// 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]]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
function ReplacementComponent({ module, match, replacement, setReplacementError }) {
|
||||||
|
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 {
|
||||||
|
var patched = src.replace(match, replacement);
|
||||||
|
setReplacementError(void 0);
|
||||||
|
} catch (e) {
|
||||||
|
setReplacementError((e as Error).message);
|
||||||
|
return ["", [], []];
|
||||||
|
}
|
||||||
|
const m = src.match(match);
|
||||||
|
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 <Forms.FormText>Regex doesn't match!</Forms.FormText>;
|
||||||
|
|
||||||
|
const fullMatch = matchResult[0] ? makeCodeblock(matchResult[0], "js") : "";
|
||||||
|
const groups = matchResult.length > 1
|
||||||
|
? makeCodeblock(matchResult.slice(1).map((g, i) => `Group ${i}: ${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 <span style={{ color }}>{p.value}</span>;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Forms.FormTitle>Module {id}</Forms.FormTitle>
|
||||||
|
|
||||||
|
{!!matchResult?.[0]?.length && (
|
||||||
|
<>
|
||||||
|
<Forms.FormTitle>Match</Forms.FormTitle>
|
||||||
|
{renderMatch()}
|
||||||
|
</>)
|
||||||
|
}
|
||||||
|
|
||||||
|
{!!diff?.length && (
|
||||||
|
<>
|
||||||
|
<Forms.FormTitle>Diff</Forms.FormTitle>
|
||||||
|
{renderDiff()}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!!diff?.length && (
|
||||||
|
<Button className={Margins.marginTop20} onClick={() => {
|
||||||
|
try {
|
||||||
|
Function(patchedCode);
|
||||||
|
setCompileResult([true, "Compiled successfully"]);
|
||||||
|
} catch (err) {
|
||||||
|
setCompileResult([false, (err as Error).message]);
|
||||||
|
}
|
||||||
|
}}>Compile</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{compileResult &&
|
||||||
|
<Forms.FormText style={{ color: compileResult[0] ? "var(--text-positive)" : "var(--text-danger)" }}>
|
||||||
|
{compileResult[1]}
|
||||||
|
</Forms.FormText>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReplacementInput({ replacement, setReplacement, replacementError }) {
|
||||||
|
const [isFunc, setIsFunc] = React.useState(false);
|
||||||
|
const [error, setError] = React.useState<string>();
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<Forms.FormTitle>replacement</Forms.FormTitle>
|
||||||
|
<TextInput
|
||||||
|
value={replacement?.toString()}
|
||||||
|
onChange={onChange}
|
||||||
|
error={error ?? replacementError}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
className={Margins.marginTop8}
|
||||||
|
value={isFunc}
|
||||||
|
onChange={setIsFunc}
|
||||||
|
note="'replacement' will be evaled if this is toggled"
|
||||||
|
hideBorder={true}
|
||||||
|
>
|
||||||
|
Treat as Function
|
||||||
|
</Switch>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PatchHelper() {
|
||||||
|
const [find, setFind] = React.useState<string>("");
|
||||||
|
const [match, setMatch] = React.useState<string>("");
|
||||||
|
const [replacement, setReplacement] = React.useState<string | Function>("");
|
||||||
|
|
||||||
|
const [replacementError, setReplacementError] = React.useState<string>();
|
||||||
|
|
||||||
|
const [module, setModule] = React.useState<[number, Function]>();
|
||||||
|
const [findError, setFindError] = React.useState<string>();
|
||||||
|
|
||||||
|
const code = React.useMemo(() => {
|
||||||
|
return `
|
||||||
|
{
|
||||||
|
find: ${JSON.stringify(find)},
|
||||||
|
replacement: {
|
||||||
|
match: /${match.replace(/(?<!\\)\//g, "\\/")}/,
|
||||||
|
replacement: ${typeof replacement === "function" ? replacement.toString() : JSON.stringify(replacement)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`.trim();
|
||||||
|
}, [find, match, replacement]);
|
||||||
|
|
||||||
|
function onFindChange(v: string) {
|
||||||
|
setFindError(void 0);
|
||||||
|
setFind(v);
|
||||||
|
if (v.length) {
|
||||||
|
findCandidates({ find: v, setModule, setError: setFindError });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMatchChange(v: string) {
|
||||||
|
try {
|
||||||
|
new RegExp(v);
|
||||||
|
setFindError(void 0);
|
||||||
|
setMatch(v);
|
||||||
|
} catch (e: any) {
|
||||||
|
setFindError((e as Error).message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Forms.FormTitle>find</Forms.FormTitle>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
value={find}
|
||||||
|
onChange={onFindChange}
|
||||||
|
error={findError}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Forms.FormTitle>match</Forms.FormTitle>
|
||||||
|
<CheckedTextInput
|
||||||
|
value={match}
|
||||||
|
onChange={onMatchChange}
|
||||||
|
validate={v => {
|
||||||
|
try {
|
||||||
|
return (new RegExp(v), true);
|
||||||
|
} catch (e) {
|
||||||
|
return (e as Error).message;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ReplacementInput
|
||||||
|
replacement={replacement}
|
||||||
|
setReplacement={setReplacement}
|
||||||
|
replacementError={replacementError}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Forms.FormDivider />
|
||||||
|
{module && (
|
||||||
|
<ReplacementComponent
|
||||||
|
module={module}
|
||||||
|
match={new RegExp(match)}
|
||||||
|
replacement={replacement}
|
||||||
|
setReplacementError={setReplacementError}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!!(find && match && replacement) && (
|
||||||
|
<>
|
||||||
|
<Forms.FormTitle className={Margins.marginTop20}>Code</Forms.FormTitle>
|
||||||
|
{Parser.parse(makeCodeblock(code, "ts"))}
|
||||||
|
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IS_DEV ? ErrorBoundary.wrap(PatchHelper) : null;
|
|
@ -29,12 +29,13 @@ import { Button, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUti
|
||||||
import ErrorBoundary from "../ErrorBoundary";
|
import ErrorBoundary from "../ErrorBoundary";
|
||||||
import { Flex } from "../Flex";
|
import { Flex } from "../Flex";
|
||||||
import {
|
import {
|
||||||
|
ISettingElementProps,
|
||||||
SettingBooleanComponent,
|
SettingBooleanComponent,
|
||||||
SettingCustomComponent,
|
SettingCustomComponent,
|
||||||
SettingInputComponent,
|
|
||||||
SettingNumericComponent,
|
SettingNumericComponent,
|
||||||
SettingSelectComponent,
|
SettingSelectComponent,
|
||||||
SettingSliderComponent
|
SettingSliderComponent,
|
||||||
|
SettingTextComponent
|
||||||
} from "./components";
|
} from "./components";
|
||||||
|
|
||||||
const UserSummaryItem = lazyWebpack(filters.byCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
|
const UserSummaryItem = lazyWebpack(filters.byCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
|
||||||
|
@ -60,6 +61,16 @@ function makeDummyUser(user: { name: string, id: BigInt; }) {
|
||||||
return newUser;
|
return newUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any>>> = {
|
||||||
|
[OptionType.STRING]: SettingTextComponent,
|
||||||
|
[OptionType.NUMBER]: SettingNumericComponent,
|
||||||
|
[OptionType.BIGINT]: SettingNumericComponent,
|
||||||
|
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
||||||
|
[OptionType.SELECT]: SettingSelectComponent,
|
||||||
|
[OptionType.SLIDER]: SettingSliderComponent,
|
||||||
|
[OptionType.COMPONENT]: SettingCustomComponent
|
||||||
|
};
|
||||||
|
|
||||||
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
||||||
const [authors, setAuthors] = React.useState<Partial<User>[]>([]);
|
const [authors, setAuthors] = React.useState<Partial<User>[]>([]);
|
||||||
|
|
||||||
|
@ -75,8 +86,10 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
for (const user of plugin.authors.slice(0, 6)) {
|
for (const user of plugin.authors.slice(0, 6)) {
|
||||||
const author = user.id ? await UserUtils.fetchUser(`${user.id}`).catch(() => null) : makeDummyUser(user);
|
const author = user.id
|
||||||
setAuthors(a => [...a, author || makeDummyUser(user)]);
|
? await UserUtils.fetchUser(`${user.id}`).catch(() => makeDummyUser(user))
|
||||||
|
: makeDummyUser(user);
|
||||||
|
setAuthors(a => [...a, author]);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -111,9 +124,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: JSX.Element[] = [];
|
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
||||||
for (const [key, setting] of Object.entries(plugin.options)) {
|
function onChange(newValue: any) {
|
||||||
function onChange(newValue) {
|
|
||||||
setTempSettings(s => ({ ...s, [key]: newValue }));
|
setTempSettings(s => ({ ...s, [key]: newValue }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,35 +133,19 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
setErrors(e => ({ ...e, [key]: hasError }));
|
setErrors(e => ({ ...e, [key]: hasError }));
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = { onChange, pluginSettings, id: key, onError };
|
const Component = Components[setting.type];
|
||||||
switch (setting.type) {
|
return (
|
||||||
case OptionType.SELECT: {
|
<Component
|
||||||
options.push(<SettingSelectComponent key={key} option={setting} {...props} />);
|
id={key}
|
||||||
break;
|
key={key}
|
||||||
}
|
option={setting}
|
||||||
case OptionType.STRING: {
|
onChange={onChange}
|
||||||
options.push(<SettingInputComponent key={key} option={setting} {...props} />);
|
onError={onError}
|
||||||
break;
|
pluginSettings={pluginSettings}
|
||||||
}
|
/>
|
||||||
case OptionType.NUMBER:
|
);
|
||||||
case OptionType.BIGINT: {
|
});
|
||||||
options.push(<SettingNumericComponent key={key} option={setting} {...props} />);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OptionType.BOOLEAN: {
|
|
||||||
options.push(<SettingBooleanComponent key={key} option={setting} {...props} />);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OptionType.SLIDER: {
|
|
||||||
options.push(<SettingSliderComponent key={key} option={setting} {...props} />);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OptionType.COMPONENT: {
|
|
||||||
options.push(<SettingCustomComponent key={key} option={setting} {...props} />);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return <Flex flexDirection="column" style={{ gap: 12 }}>{options}</Flex>;
|
return <Flex flexDirection="column" style={{ gap: 12 }}>{options}</Flex>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { PluginOptionString } from "../../../utils/types";
|
||||||
import { Forms, React, TextInput } from "../../../webpack/common";
|
import { Forms, React, TextInput } from "../../../webpack/common";
|
||||||
import { ISettingElementProps } from ".";
|
import { ISettingElementProps } from ".";
|
||||||
|
|
||||||
export function SettingInputComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionString>) {
|
export function SettingTextComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionString>) {
|
||||||
const [state, setState] = React.useState(pluginSettings[id] ?? option.default ?? null);
|
const [state, setState] = React.useState(pluginSettings[id] ?? option.default ?? null);
|
||||||
const [error, setError] = React.useState<string | null>(null);
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
|
|
||||||
|
|
|
@ -220,11 +220,6 @@ export default ErrorBoundary.wrap(function Settings() {
|
||||||
return o;
|
return o;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function hasDependents(plugin: Plugin) {
|
|
||||||
const enabledDependants = depMap[plugin.name]?.filter(d => settings.plugins[d].enabled);
|
|
||||||
return !!enabledDependants?.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sortedPlugins = React.useMemo(() => Object.values(Plugins)
|
const sortedPlugins = React.useMemo(() => Object.values(Plugins)
|
||||||
.sort((a, b) => a.name.localeCompare(b.name)), []);
|
.sort((a, b) => a.name.localeCompare(b.name)), []);
|
||||||
|
|
||||||
|
@ -264,7 +259,7 @@ export default ErrorBoundary.wrap(function Settings() {
|
||||||
{ label: "Show Enabled", value: "enabled" },
|
{ label: "Show Enabled", value: "enabled" },
|
||||||
{ label: "Show Disabled", value: "disabled" }
|
{ label: "Show Disabled", value: "disabled" }
|
||||||
]}
|
]}
|
||||||
serialize={v => String(v)}
|
serialize={String}
|
||||||
select={onStatusChange}
|
select={onStatusChange}
|
||||||
isSelected={v => v === searchValue.status}
|
isSelected={v => v === searchValue.status}
|
||||||
closeOnSelect={true}
|
closeOnSelect={true}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export { default as PatchHelper } from "./PatchHelper";
|
||||||
export { default as PluginSettings } from "./PluginSettings";
|
export { default as PluginSettings } from "./PluginSettings";
|
||||||
export { default as Settings } from "./Settings";
|
export { default as Settings } from "./Settings";
|
||||||
export { default as Updater } from "./Updater";
|
export { default as Updater } from "./Updater";
|
||||||
|
|
|
@ -55,11 +55,13 @@ export default definePlugin({
|
||||||
match: /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.ACTIVITY_SETTINGS\}/,
|
match: /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.ACTIVITY_SETTINGS\}/,
|
||||||
replace: (m, mod) => {
|
replace: (m, mod) => {
|
||||||
const updater = !IS_WEB ? '{section:"VencordUpdater",label:"Updater",element:Vencord.Components.Updater},' : "";
|
const updater = !IS_WEB ? '{section:"VencordUpdater",label:"Updater",element:Vencord.Components.Updater},' : "";
|
||||||
|
const patchHelper = IS_DEV ? '{section:"VencordPatchHelper",label:"PatchHelper",element:Vencord.Components.PatchHelper},' : "";
|
||||||
return (
|
return (
|
||||||
`{section:${mod}.ID.HEADER,label:"Vencord"},` +
|
`{section:${mod}.ID.HEADER,label:"Vencord"},` +
|
||||||
'{section:"VencordSetting",label:"Vencord",element:Vencord.Components.Settings},' +
|
'{section:"VencordSetting",label:"Vencord",element:Vencord.Components.Settings},' +
|
||||||
'{section:"VencordPlugins",label:"Plugins",element:Vencord.Components.PluginSettings},' +
|
'{section:"VencordPlugins",label:"Plugins",element:Vencord.Components.PluginSettings},' +
|
||||||
updater +
|
updater +
|
||||||
|
patchHelper +
|
||||||
`{section:${mod}.ID.DIVIDER},${m}`
|
`{section:${mod}.ID.DIVIDER},${m}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ export const Margins = lazyWebpack(filters.byProps("marginTop20"));
|
||||||
|
|
||||||
export let FluxDispatcher: Other.FluxDispatcher;
|
export let FluxDispatcher: Other.FluxDispatcher;
|
||||||
export let React: typeof import("react");
|
export let React: typeof import("react");
|
||||||
|
export const ReactDOM: typeof import("react-dom") = lazyWebpack(filters.byProps("createPortal", "render"));
|
||||||
|
|
||||||
export let GuildStore: Stores.GuildStore;
|
export let GuildStore: Stores.GuildStore;
|
||||||
export let UserStore: Stores.UserStore;
|
export let UserStore: Stores.UserStore;
|
||||||
|
|
Loading…
Reference in a new issue