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 { Flex } from "@components/Flex";
import { copyWithToast } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { Text } from "@webpack/common";
import type { ReactNode } from "react";
import { Button, showToast, Text, Toasts } from "@webpack/common";
import { type ReactNode } from "react";
import { UserstyleHeader } from "usercss-meta";
import { SettingBooleanComponent, SettingColorComponent, SettingNumberComponent, SettingRangeComponent, SettingSelectComponent, SettingTextComponent } from "./components";
@ -107,7 +108,36 @@ export function UserCSSSettingsModal({ modalProps, theme }: UserCSSSettingsModal
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<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>
</ModalRoot>
);

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@
* 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 {
label: string;
@ -17,13 +17,9 @@ interface Props {
}
export function SettingRangeComponent({ label, name, default: def, min, max, step, themeSettings }: Props) {
const [value, setValue] = useState(themeSettings[name]);
function handleChange(value: number) {
const corrected = value.toString();
setValue(corrected);
themeSettings[name] = corrected;
}
@ -42,7 +38,7 @@ export function SettingRangeComponent({ label, name, default: def, min, max, ste
<Forms.FormSection>
<Forms.FormTitle tag="h5">{label}</Forms.FormTitle>
<Slider
initialValue={parseInt(value, 10)}
initialValue={parseInt(themeSettings[name], 10)}
defaultValue={def}
onValueChange={handleChange}
minValue={min}

View file

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

View file

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

View file

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