feat: better buttons and icons for theme import/export

This commit is contained in:
Lewis Crichton 2023-12-28 13:50:29 +00:00
parent 18b1fe0413
commit 9b89ef58be
No known key found for this signature in database
3 changed files with 87 additions and 33 deletions

View file

@ -308,3 +308,21 @@ export function NoEntrySignIcon(props: IconProps) {
</Icon> </Icon>
); );
} }
export function PasteIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-paste-icon")}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
</Icon>
);
}

View file

@ -4,11 +4,12 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { useSettings } from "@api/Settings"; import { Settings, useSettings } from "@api/Settings";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { CopyIcon, PasteIcon } from "@components/Icons";
import { copyWithToast } from "@utils/misc"; import { copyWithToast } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot } from "@utils/modal"; import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { Button, showToast, Text, Toasts } from "@webpack/common"; import { showToast, Text, Toasts, Tooltip } from "@webpack/common";
import { type ReactNode } from "react"; import { type ReactNode } from "react";
import { UserstyleHeader } from "usercss-meta"; import { UserstyleHeader } from "usercss-meta";
@ -19,6 +20,59 @@ interface UserCSSSettingsModalProps {
theme: UserstyleHeader; theme: UserstyleHeader;
} }
function ExportButton({ themeSettings }: { themeSettings: Settings["userCssVars"][""]; }) {
return <Tooltip text={"Copy theme settings"}>
{({ onMouseLeave, onMouseEnter }) => (
<div
style={{ cursor: "pointer" }}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={() => {
copyWithToast(JSON.stringify(themeSettings), "Copied theme settings to clipboard.");
}}>
<CopyIcon />
</div>
)}
</Tooltip>;
}
function ImportButton({ themeSettings }: { themeSettings: Settings["userCssVars"][""]; }) {
return <Tooltip text={"Paste theme settings"}>
{({ onMouseLeave, onMouseEnter }) => (
<div
style={{ cursor: "pointer" }}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
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("Pasted theme settings from clipboard.", Toasts.Type.SUCCESS);
}}>
<PasteIcon />
</div>
)}
</Tooltip>;
}
export function UserCSSSettingsModal({ modalProps, theme }: UserCSSSettingsModalProps) { export function UserCSSSettingsModal({ modalProps, theme }: UserCSSSettingsModalProps) {
// @ts-expect-error UseSettings<> can't determine this is a valid key // @ts-expect-error UseSettings<> can't determine this is a valid key
const themeSettings = useSettings(["userCssVars"], false).userCssVars[theme.id]; const themeSettings = useSettings(["userCssVars"], false).userCssVars[theme.id];
@ -105,37 +159,14 @@ export function UserCSSSettingsModal({ modalProps, theme }: UserCSSSettingsModal
<ModalRoot {...modalProps}> <ModalRoot {...modalProps}>
<ModalHeader separator={false}> <ModalHeader separator={false}>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Settings for {theme.name}</Text> <Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Settings for {theme.name}</Text>
<Flex style={{ gap: 4, marginRight: 4 }} className="vc-settings-usercss-ie-buttons">
<ExportButton themeSettings={themeSettings} />
<ImportButton themeSettings={themeSettings} />
</Flex>
<ModalCloseButton onClick={modalProps.onClose} /> <ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader> </ModalHeader>
<ModalContent> <ModalContent>
<Flex flexDirection="column" style={{ gap: 12, marginBottom: 16 }}> <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} {controls}
</Flex> </Flex>
</ModalContent> </ModalContent>

View file

@ -28,8 +28,13 @@
content: "by "; content: "by ";
} }
.vc-settings-usercss-ie-grid { .vc-settings-usercss-ie-buttons > div {
display: grid; color: var(--interactive-normal);
grid-template-columns: repeat(2, 1fr); opacity: .5;
grid-gap: 12px; /* matching the flex gap */ padding: 4px;
}
.vc-settings-usercss-ie-buttons > div:hover {
color: var(--interactive-hover);
opacity: 1;
} }