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 { 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>
|
||||
);
|
||||
|
|
|
@ -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" }}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 */
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue