Files
xrpl-hooks-ide/components/RunScript/index.tsx
2022-06-20 14:16:16 +03:00

250 lines
7.7 KiB
TypeScript

import * as Handlebars from "handlebars";
import { Play, X } from "phosphor-react";
import { useEffect, useState } from "react";
import state, { IFile, ILog } from "../../state";
import Button from "../Button";
import Box from "../Box";
import Input from "../Input";
import Stack from "../Stack";
import {
Dialog,
DialogTrigger,
DialogContent,
DialogTitle,
DialogDescription,
DialogClose,
} from "../Dialog";
import Flex from "../Flex";
import { useSnapshot } from "valtio";
import Select from "../Select";
Handlebars.registerHelper("input", function (/* dynamic arguments */) {
return new Handlebars.SafeString(arguments[0]);
});
const generateHtmlTemplate = (code: string) => {
return `
<html>
<head>
<script>
var log = console.log;
var errorLog = console.error;
var infoLog = console.info;
var warnLog = console.warn
console.log = function(){
var args = Array.from(arguments);
parent.window.postMessage({ type: 'log', args: args || [] }, '*');
log.apply(console, args);
}
console.error = function(){
var args = Array.from(arguments);
parent.window.postMessage({ type: 'error', args: args || [] }, '*');
errorLog.apply(console, args);
}
console.info = function(){
var args = Array.from(arguments);
parent.window.postMessage({ type: 'info', args: args || [] }, '*');
infoLog.apply(console, args);
}
console.warn = function(){
var args = Array.from(arguments);
parent.window.postMessage({ type: 'warning', args: args || [] }, '*');
warnLog.apply(console, args);
}
</script>
<script type="module">
${code}
</script>
</head>
<body>
</body>
</html>
`;
};
type Fields = Record<
string,
{
key: string;
value: string | { value: string; label: string };
label?: string;
type?: string;
attach?: "account_secret" | "account_address" | string;
}
>;
const RunScript: React.FC<{ file: IFile }> = ({ file }) => {
const snap = useSnapshot(state);
const parsed = Handlebars.parse(file.content);
const names = parsed.body
.filter((i) => i.type === "MustacheStatement")
.map((block) => {
// @ts-expect-error
const type = block.hash?.pairs?.find((i) => i.key == "type");
// @ts-expect-error
const attach = block.hash?.pairs?.find((i) => i.key == "attach");
// @ts-expect-error
const label = block.hash?.pairs?.find((i) => i.key == "title");
const key =
// @ts-expect-error
block?.path?.original === "input"
? // @ts-expect-error
block?.params?.[0].original
: // @ts-expect-error
block?.path?.original;
return {
key,
label: label?.value?.original || key,
attach: attach?.value?.original,
type: type?.value?.original,
value: "",
};
});
const defaultState: Fields = {};
names.forEach((field) => (defaultState[field.key] = field));
const [fields, setFields] = useState<Fields>(defaultState);
const [iFrameCode, setIframeCode] = useState("");
const [isDialogOpen, setIsDialogOpen] = useState(false);
const runScript = () => {
const fieldsToSend: Record<string, string> = {};
Object.entries(fields).map(([key, obj]) => {
fieldsToSend[key] =
typeof obj.value === "string" ? obj.value : obj.value.value;
});
const template = Handlebars.compile(file.content);
const code = template(fieldsToSend);
setIframeCode(generateHtmlTemplate(code));
};
useEffect(() => {
const handleEvent = (e: any) => {
if (e.data.type === "log" || e.data.type === "error") {
const data: ILog[] = e.data.args.map((msg: any) => ({
type: e.data.type,
message: typeof msg === "string" ? msg : JSON.stringify(msg, null, 2),
}));
state.scriptLogs = [...snap.scriptLogs, ...data];
}
};
window.addEventListener("message", handleEvent);
return () => window.removeEventListener("message", handleEvent);
}, [snap.scriptLogs]);
return (
<>
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Button
variant="primary"
onClick={() => {
setIframeCode("");
}}
>
{file.name} <Play weight="bold" size="16px" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>Run {file.name} script</DialogTitle>
<DialogDescription>
You are about to run scripts provided by the developer of the hook,
make sure you know what you are doing.
<br />
<br />
{Object.keys(fields).length > 0
? `You also need to fill in following parameters to run the script`
: ""}
</DialogDescription>
<Stack css={{ width: "100%" }}>
{Object.keys(fields).map((key) => (
<Box key={key} css={{ width: "100%" }}>
<label>{fields[key]?.label || key}</label>
{fields[key].type === "select" ? (
<Select
options={snap.accounts.map((acc) => ({
label: acc.name,
value:
fields[key].attach === "account_secret"
? acc.secret
: acc.address,
}))}
onChange={(val: any) => {
setFields({
...fields,
[key]: { ...fields[key], value: val },
});
}}
value={fields[key].value}
/>
) : (
<Input
type={fields[key].type || "text"}
value={
typeof fields[key].value !== "string"
? // @ts-expect-error
fields[key].value.value
: fields[key].value
}
css={{ mt: "$1" }}
onChange={(e) => {
setFields({
...fields,
[key]: { ...fields[key], value: e.target.value },
});
}}
/>
)}
</Box>
))}
<Flex
css={{ justifyContent: "flex-end", width: "100%", gap: "$3" }}
>
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
<Button
variant="primary"
isDisabled={
Object.entries(fields).length > 0 &&
Object.entries(fields).every(([key, value]) => !value.value)
}
onClick={() => {
state.scriptLogs = [];
runScript();
setIsDialogOpen(false);
}}
>
Run script
</Button>
</Flex>
</Stack>
<DialogClose asChild>
<Box
css={{
position: "absolute",
top: "$1",
right: "$1",
cursor: "pointer",
background: "$mauve1",
display: "flex",
borderRadius: "$full",
p: "$1",
}}
>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
{iFrameCode && (
<iframe
style={{ display: "none" }}
srcDoc={iFrameCode}
sandbox="allow-scripts"
/>
)}
</>
);
};
export default RunScript;