Reimplement Discord's Switch to fix performance (#413)
This commit is contained in:
parent
e49151ff33
commit
1d287357ca
@ -23,25 +23,25 @@ import { showNotice } from "@api/Notices";
|
|||||||
import { useSettings } from "@api/settings";
|
import { useSettings } from "@api/settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { handleComponentFailed } from "@components/handleComponentFailed";
|
import { handleComponentFailed } from "@components/handleComponentFailed";
|
||||||
import { Badge } from "@components/PluginSettings/components";
|
import { Badge } from "@components/PluginSettings/components";
|
||||||
import PluginModal from "@components/PluginSettings/PluginModal";
|
import PluginModal from "@components/PluginSettings/PluginModal";
|
||||||
|
import { Switch } from "@components/Switch";
|
||||||
import { ChangeList } from "@utils/ChangeList";
|
import { ChangeList } from "@utils/ChangeList";
|
||||||
import Logger from "@utils/Logger";
|
import Logger from "@utils/Logger";
|
||||||
import { classes, LazyComponent, useAwaiter } from "@utils/misc";
|
import { classes, LazyComponent, useAwaiter } from "@utils/misc";
|
||||||
import { openModalLazy } from "@utils/modal";
|
import { openModalLazy } from "@utils/modal";
|
||||||
import { Plugin } from "@utils/types";
|
import { Plugin } from "@utils/types";
|
||||||
import { findByCode, findByPropsLazy } from "@webpack";
|
import { findByCode, findByPropsLazy } from "@webpack";
|
||||||
import { Alerts, Button, Forms, Margins, Parser, React, Select, Switch, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
|
import { Alerts, Button, Card, Forms, Margins, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
const cl = classNameFactory("vc-plugins-");
|
|
||||||
|
|
||||||
import { startDependenciesRecursive, startPlugin, stopPlugin } from "../../plugins";
|
import { startDependenciesRecursive, startPlugin, stopPlugin } from "../../plugins";
|
||||||
|
|
||||||
|
|
||||||
|
const cl = classNameFactory("vc-plugins-");
|
||||||
const logger = new Logger("PluginSettings", "#a6d189");
|
const logger = new Logger("PluginSettings", "#a6d189");
|
||||||
|
|
||||||
const InputStyles = findByPropsLazy("inputDefault", "inputWrapper");
|
const InputStyles = findByPropsLazy("inputDefault", "inputWrapper");
|
||||||
@ -60,23 +60,27 @@ function showErrorToast(message: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ReloadRequiredCardProps extends React.HTMLProps<HTMLDivElement> {
|
function ReloadRequiredCard({ required }: { required: boolean; }) {
|
||||||
plugins: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function ReloadRequiredCard({ plugins, ...props }: ReloadRequiredCardProps) {
|
|
||||||
if (plugins.length === 0) return null;
|
|
||||||
|
|
||||||
const pluginPrefix = plugins.length === 1 ? "The plugin" : "The following plugins require a reload to apply changes:";
|
|
||||||
const pluginSuffix = plugins.length === 1 ? " requires a reload to apply changes." : ".";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorCard {...props} className={cl("reload-card")}>
|
<Card className={cl("info-card", { "restart-card": required })}>
|
||||||
<span className={cl("dep-text")}>
|
{required ? (
|
||||||
{pluginPrefix} <code>{plugins.join(", ")}</code>{pluginSuffix}
|
<>
|
||||||
</span>
|
<Forms.FormTitle tag="h5">Restart required!</Forms.FormTitle>
|
||||||
<Button look={Button.Looks.INVERTED} onClick={() => location.reload()}>Reload</Button>
|
<Forms.FormText className={cl("dep-text")}>
|
||||||
</ErrorCard>
|
Restart now to apply new plugins and their settings
|
||||||
|
</Forms.FormText>
|
||||||
|
<Button color={Button.Colors.YELLOW} onClick={() => location.reload()}>
|
||||||
|
Restart
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Forms.FormTitle tag="h5">Plugin Management</Forms.FormTitle>
|
||||||
|
<Forms.FormText>Press the cog wheel or info icon to get more info on a plugin</Forms.FormText>
|
||||||
|
<Forms.FormText>Plugins with a cog wheel have settings you can modify!</Forms.FormText>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,26 +151,24 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex className={cl("card")} flexDirection="column" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
<Flex className={cl("card", { "card-disabled": disabled })} flexDirection="column" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||||
<Switch
|
<div className={cl("card-header")}>
|
||||||
onChange={toggleEnabled}
|
<Text variant="text-md/bold" className={cl("name")}>
|
||||||
disabled={disabled}
|
{plugin.name}{isNew && <Badge text="NEW" color="#ED4245" />}
|
||||||
value={isEnabled()}
|
</Text>
|
||||||
note={<Text className={cl("note")} variant="text-sm/normal">{plugin.description}</Text>}
|
<button role="switch" onClick={() => openModal()} className={classes("button-12Fmur", cl("info-button"))}>
|
||||||
hideBorder={true}
|
{plugin.options
|
||||||
>
|
? <CogWheel />
|
||||||
<Flex className={cl("flex")}>
|
: <InfoIcon width="24" height="24" />}
|
||||||
<Text variant="text-md/bold" className={cl("name")}>
|
</button>
|
||||||
{plugin.name}{isNew && <Badge text="NEW" color="#ED4245" />}
|
<Switch
|
||||||
</Text>
|
checked={isEnabled()}
|
||||||
<button role="switch" onClick={() => openModal()} className={classes("button-12Fmur", cl("info-button"))}>
|
onChange={toggleEnabled}
|
||||||
{plugin.options
|
disabled={disabled}
|
||||||
? <CogWheel />
|
/>
|
||||||
: <InfoIcon width="24" height="24" />}
|
</div>
|
||||||
</button>
|
<Text className={cl("note")} variant="text-sm/normal">{plugin.description}</Text>
|
||||||
</Flex>
|
</Flex >
|
||||||
</Switch>
|
|
||||||
</Flex>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,12 +300,12 @@ export default ErrorBoundary.wrap(function PluginSettings() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
|
<ReloadRequiredCard required={changes.hasChanges} />
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}>
|
<Forms.FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}>
|
||||||
Filters
|
Filters
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
|
|
||||||
<ReloadRequiredCard plugins={[...changes.getChanges()]} className={Margins.marginBottom20} />
|
|
||||||
|
|
||||||
<div className={cl("filter-controls")}>
|
<div className={cl("filter-controls")}>
|
||||||
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} className={Margins.marginBottom20} />
|
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} className={Margins.marginBottom20} />
|
||||||
<div className={InputStyles.inputWrapper}>
|
<div className={InputStyles.inputWrapper}>
|
||||||
|
@ -29,18 +29,32 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
display: block;
|
display: block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 10px;
|
padding: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transition: 0.1s ease-out;
|
transition: 0.1s ease-out;
|
||||||
transition-property: box-shadow, transform, background, opacity;
|
transition-property: box-shadow, transform, background, opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-plugins-card-disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
.vc-plugins-card:hover {
|
.vc-plugins-card:hover {
|
||||||
background-color: var(--background-tertiary);
|
background-color: var(--background-tertiary);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: var(--elevation-high);
|
box-shadow: var(--elevation-high);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-plugins-card-header {
|
||||||
|
margin-top: auto;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-end;
|
||||||
|
height: 1.5rem;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.vc-plugins-info-button {
|
.vc-plugins-info-button {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
@ -92,23 +106,30 @@
|
|||||||
cursor: "default";
|
cursor: "default";
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-plugins-flex {
|
|
||||||
margin-top: auto;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-plugins-dep-name {
|
.vc-plugins-dep-name {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-plugins-reload-card {
|
.vc-plugins-info-card {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
display: grid;
|
height: 8em;
|
||||||
grid-template-columns: 1fr auto;
|
display: flex;
|
||||||
gap: 1em;
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-plugins-info-card div {
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-plugins-restart-card {
|
||||||
|
padding: 1em;
|
||||||
|
background: var(--info-warning-background);
|
||||||
|
border: 1px solid var(--info-warning-foreground);
|
||||||
|
color: var(--info-warning-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-plugins-restart-card button {
|
||||||
|
margin-top: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-plugins-info-button svg:not(:hover):not(:focus) {
|
.vc-plugins-info-button svg:not(:hover):not(:focus) {
|
||||||
|
3
src/components/Switch.css
Normal file
3
src/components/Switch.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.vc-switch-slider {
|
||||||
|
transition: 100ms transform ease-in-out;
|
||||||
|
}
|
76
src/components/Switch.tsx
Normal file
76
src/components/Switch.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./Switch.css";
|
||||||
|
|
||||||
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
|
||||||
|
interface SwitchProps {
|
||||||
|
checked: boolean;
|
||||||
|
onChange: (checked: boolean) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SWITCH_ON = "var(--status-green-600)";
|
||||||
|
const SWITCH_OFF = "var(--primary-dark-400)";
|
||||||
|
const SwitchClasses = findByPropsLazy("slider", "input", "container");
|
||||||
|
|
||||||
|
export function Switch({ checked, onChange, disabled }: SwitchProps) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={`${SwitchClasses.container} default-colors`} style={{
|
||||||
|
backgroundColor: checked ? SWITCH_ON : SWITCH_OFF,
|
||||||
|
opacity: disabled ? 0.3 : 1
|
||||||
|
}}>
|
||||||
|
<svg
|
||||||
|
className={SwitchClasses.slider + " vc-switch-slider"}
|
||||||
|
viewBox="0 0 28 20"
|
||||||
|
preserveAspectRatio="xMinYMid meet"
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{
|
||||||
|
transform: checked ? "translateX(12px)" : "translateX(-3px)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<rect fill="white" x="4" y="0" height="20" width="20" rx="10" />
|
||||||
|
<svg viewBox="0 0 20 20" fill="none">
|
||||||
|
{checked ? (
|
||||||
|
<>
|
||||||
|
<path fill={SWITCH_ON} d="M7.89561 14.8538L6.30462 13.2629L14.3099 5.25755L15.9009 6.84854L7.89561 14.8538Z" />
|
||||||
|
<path fill={SWITCH_ON} d="M4.08643 11.0903L5.67742 9.49929L9.4485 13.2704L7.85751 14.8614L4.08643 11.0903Z" />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<path fill={SWITCH_OFF} d="M5.13231 6.72963L6.7233 5.13864L14.855 13.2704L13.264 14.8614L5.13231 6.72963Z" />
|
||||||
|
<path fill={SWITCH_OFF} d="M13.2704 5.13864L14.8614 6.72963L6.72963 14.8614L5.13864 13.2704L13.2704 5.13864Z" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
disabled={disabled}
|
||||||
|
type="checkbox"
|
||||||
|
className={SwitchClasses.input}
|
||||||
|
tabIndex={0}
|
||||||
|
checked={checked}
|
||||||
|
onChange={e => onChange(e.currentTarget.checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -49,9 +49,11 @@ if (location.protocol !== "data:") {
|
|||||||
const css = readFileSync(rendererCss, "utf-8");
|
const css = readFileSync(rendererCss, "utf-8");
|
||||||
insertCss(css);
|
insertCss(css);
|
||||||
if (IS_DEV) {
|
if (IS_DEV) {
|
||||||
watch(rendererCss, debounce(() => {
|
// persistent means keep process running if watcher is the only thing still running
|
||||||
|
// which we obviously don't want
|
||||||
|
watch(rendererCss, { persistent: false }, () => {
|
||||||
document.getElementById("vencord-css-core")!.textContent = readFileSync(rendererCss, "utf-8");
|
document.getElementById("vencord-css-core")!.textContent = readFileSync(rendererCss, "utf-8");
|
||||||
}, 30));
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if ((err as NodeJS.ErrnoException)?.code !== "ENOENT")
|
if ((err as NodeJS.ErrnoException)?.code !== "ENOENT")
|
||||||
|
Loading…
Reference in New Issue
Block a user