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:
parent
85bd99c2ca
commit
18b1fe0413
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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" }}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 */
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue