feat: clipboard copying of theme settings

this also removes the stupid state management that every theme setting
had, which I do not remember the justification for but was completely
unnecessary and broke rendering when you update the settings from
outside of these components
This commit is contained in:
Lewis Crichton 2023-12-28 13:23:18 +00:00
parent 85bd99c2ca
commit 18b1fe0413
No known key found for this signature in database
8 changed files with 51 additions and 39 deletions

View file

@ -6,9 +6,10 @@
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { copyWithToast } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot } from "@utils/modal"; import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { Text } from "@webpack/common"; import { Button, showToast, Text, Toasts } from "@webpack/common";
import type { ReactNode } from "react"; import { type ReactNode } from "react";
import { UserstyleHeader } from "usercss-meta"; import { UserstyleHeader } from "usercss-meta";
import { SettingBooleanComponent, SettingColorComponent, SettingNumberComponent, SettingRangeComponent, SettingSelectComponent, SettingTextComponent } from "./components"; import { SettingBooleanComponent, SettingColorComponent, SettingNumberComponent, SettingRangeComponent, SettingSelectComponent, SettingTextComponent } from "./components";
@ -107,7 +108,36 @@ export function UserCSSSettingsModal({ modalProps, theme }: UserCSSSettingsModal
<ModalCloseButton onClick={modalProps.onClose} /> <ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader> </ModalHeader>
<ModalContent> <ModalContent>
<Flex flexDirection="column" style={{ gap: 12, marginBottom: 16 }}>{controls}</Flex> <Flex flexDirection="column" style={{ gap: 12, marginBottom: 16 }}>
<div className="vc-settings-usercss-ie-grid">
<Button size={Button.Sizes.SMALL} onClick={() => {
copyWithToast(JSON.stringify(themeSettings), "Copied theme settings to clipboard.");
}}>Export</Button>
<Button size={Button.Sizes.SMALL} onClick={async () => {
const clip = (await navigator.clipboard.read())[0];
if (!clip) return showToast("Your clipboard is empty.", Toasts.Type.FAILURE);
if (!clip.types.includes("text/plain"))
return showToast("Your clipboard doesn't have valid settings data.", Toasts.Type.FAILURE);
try {
var potentialSettings: Record<string, string> =
JSON.parse(await clip.getType("text/plain").then(b => b.text()));
} catch (e) {
return showToast("Your clipboard doesn't have valid settings data.", Toasts.Type.FAILURE);
}
for (const [key, value] of Object.entries(potentialSettings)) {
if (Object.prototype.hasOwnProperty.call(themeSettings, key))
themeSettings[key] = value;
}
showToast("Imported theme settings from clipboard.", Toasts.Type.SUCCESS);
}}>Import</Button>
</div>
{controls}
</Flex>
</ModalContent> </ModalContent>
</ModalRoot> </ModalRoot>
); );

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { Forms, Switch, useState } from "@webpack/common"; import { Forms, Switch } from "@webpack/common";
interface Props { interface Props {
label: string; label: string;
@ -13,13 +13,9 @@ interface Props {
} }
export function SettingBooleanComponent({ label, name, themeSettings }: Props) { export function SettingBooleanComponent({ label, name, themeSettings }: Props) {
const [value, setValue] = useState(themeSettings[name]);
function handleChange(value: boolean) { function handleChange(value: boolean) {
const corrected = value ? "1" : "0"; const corrected = value ? "1" : "0";
setValue(corrected);
themeSettings[name] = corrected; themeSettings[name] = corrected;
} }
@ -27,7 +23,7 @@ export function SettingBooleanComponent({ label, name, themeSettings }: Props) {
<Forms.FormSection> <Forms.FormSection>
<Switch <Switch
key={name} key={name}
value={value === "1"} value={themeSettings[name] === "1"}
onChange={handleChange} onChange={handleChange}
hideBorder hideBorder
style={{ marginBottom: "0.5em" }} style={{ marginBottom: "0.5em" }}

View file

@ -8,7 +8,7 @@ import "./colorStyles.css";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { findByCodeLazy, findComponentByCodeLazy } from "@webpack"; import { findByCodeLazy, findComponentByCodeLazy } from "@webpack";
import { Forms, useMemo, useState } from "@webpack/common"; import { Forms, useMemo } from "@webpack/common";
interface ColorPickerProps { interface ColorPickerProps {
color: number | null; color: number | null;
@ -29,17 +29,13 @@ interface Props {
} }
export function SettingColorComponent({ label, name, themeSettings }: Props) { export function SettingColorComponent({ label, name, themeSettings }: Props) {
const [value, setValue] = useState(themeSettings[name]);
function handleChange(value: number) { function handleChange(value: number) {
const corrected = "#" + (value?.toString(16).padStart(6, "0") ?? "000000"); const corrected = "#" + (value?.toString(16).padStart(6, "0") ?? "000000");
setValue(corrected);
themeSettings[name] = corrected; themeSettings[name] = corrected;
} }
const normalizedValue = useMemo(() => parseInt(TinyColor(value).toHex(), 16), [value]); const normalizedValue = useMemo(() => parseInt(TinyColor(themeSettings[name]).toHex(), 16), [themeSettings[name]]);
return ( return (
<Forms.FormSection> <Forms.FormSection>

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { Forms, TextInput, useState } from "@webpack/common"; import { Forms, TextInput } from "@webpack/common";
interface Props { interface Props {
label: string; label: string;
@ -13,11 +13,7 @@ interface Props {
} }
export function SettingNumberComponent({ label, name, themeSettings }: Props) { export function SettingNumberComponent({ label, name, themeSettings }: Props) {
const [value, setValue] = useState(themeSettings[name]);
function handleChange(value: string) { function handleChange(value: string) {
setValue(value);
themeSettings[name] = value; themeSettings[name] = value;
} }
@ -28,7 +24,7 @@ export function SettingNumberComponent({ label, name, themeSettings }: Props) {
type="number" type="number"
pattern="-?[0-9]+" pattern="-?[0-9]+"
key={name} key={name}
value={value} value={themeSettings[name]}
onChange={handleChange} onChange={handleChange}
/> />
</Forms.FormSection> </Forms.FormSection>

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { Forms, Slider, useMemo, useState } from "@webpack/common"; import { Forms, Slider, useMemo } from "@webpack/common";
interface Props { interface Props {
label: string; label: string;
@ -17,13 +17,9 @@ interface Props {
} }
export function SettingRangeComponent({ label, name, default: def, min, max, step, themeSettings }: Props) { export function SettingRangeComponent({ label, name, default: def, min, max, step, themeSettings }: Props) {
const [value, setValue] = useState(themeSettings[name]);
function handleChange(value: number) { function handleChange(value: number) {
const corrected = value.toString(); const corrected = value.toString();
setValue(corrected);
themeSettings[name] = corrected; themeSettings[name] = corrected;
} }
@ -42,7 +38,7 @@ export function SettingRangeComponent({ label, name, default: def, min, max, ste
<Forms.FormSection> <Forms.FormSection>
<Forms.FormTitle tag="h5">{label}</Forms.FormTitle> <Forms.FormTitle tag="h5">{label}</Forms.FormTitle>
<Slider <Slider
initialValue={parseInt(value, 10)} initialValue={parseInt(themeSettings[name], 10)}
defaultValue={def} defaultValue={def}
onValueChange={handleChange} onValueChange={handleChange}
minValue={min} minValue={min}

View file

@ -5,7 +5,7 @@
*/ */
import { identity } from "@utils/misc"; import { identity } from "@utils/misc";
import { ComponentTypes, Forms, Select, useMemo, useState } from "@webpack/common"; import { ComponentTypes, Forms, Select, useMemo } from "@webpack/common";
interface Props { interface Props {
label: string; label: string;
@ -20,11 +20,7 @@ interface Props {
} }
export function SettingSelectComponent({ label, name, options, default: def, themeSettings }: Props) { export function SettingSelectComponent({ label, name, options, default: def, themeSettings }: Props) {
const [value, setValue] = useState(themeSettings[name]);
function handleChange(value: string) { function handleChange(value: string) {
setValue(value);
themeSettings[name] = value; themeSettings[name] = value;
} }
@ -47,7 +43,7 @@ export function SettingSelectComponent({ label, name, options, default: def, the
closeOnSelect={true} closeOnSelect={true}
select={handleChange} select={handleChange}
isSelected={v => v === value} isSelected={v => v === themeSettings[name]}
serialize={identity} serialize={identity}
/> />
</Forms.FormSection> </Forms.FormSection>

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { Forms, TextInput, useState } from "@webpack/common"; import { Forms, TextInput } from "@webpack/common";
interface Props { interface Props {
label: string; label: string;
@ -13,11 +13,7 @@ interface Props {
} }
export function SettingTextComponent({ label, name, themeSettings }: Props) { export function SettingTextComponent({ label, name, themeSettings }: Props) {
const [value, setValue] = useState(themeSettings[name]);
function handleChange(value: string) { function handleChange(value: string) {
setValue(value);
themeSettings[name] = value; themeSettings[name] = value;
} }
@ -26,7 +22,7 @@ export function SettingTextComponent({ label, name, themeSettings }: Props) {
<Forms.FormTitle tag="h5">{label}</Forms.FormTitle> <Forms.FormTitle tag="h5">{label}</Forms.FormTitle>
<TextInput <TextInput
key={name} key={name}
value={value} value={themeSettings[name]}
onChange={handleChange} onChange={handleChange}
/> />
</Forms.FormSection> </Forms.FormSection>

View file

@ -27,3 +27,9 @@
.vc-settings-theme-author::before { .vc-settings-theme-author::before {
content: "by "; content: "by ";
} }
.vc-settings-usercss-ie-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 12px; /* matching the flex gap */
}