Add logic for scripts
This commit is contained in:
230
components/LogBoxForScripts.tsx
Normal file
230
components/LogBoxForScripts.tsx
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
import {
|
||||||
|
useRef,
|
||||||
|
useLayoutEffect,
|
||||||
|
ReactNode,
|
||||||
|
FC,
|
||||||
|
useState,
|
||||||
|
useCallback,
|
||||||
|
} from "react";
|
||||||
|
import { FileJs, Prohibit } from "phosphor-react";
|
||||||
|
import useStayScrolled from "react-stay-scrolled";
|
||||||
|
import NextLink from "next/link";
|
||||||
|
|
||||||
|
import Container from "./Container";
|
||||||
|
import LogText from "./LogText";
|
||||||
|
import state, { ILog } from "../state";
|
||||||
|
import { Pre, Link, Heading, Button, Text, Flex, Box } from ".";
|
||||||
|
import regexifyString from "regexify-string";
|
||||||
|
import { useSnapshot } from "valtio";
|
||||||
|
import { AccountDialog } from "./Accounts";
|
||||||
|
import RunScript from "./RunScript";
|
||||||
|
|
||||||
|
interface ILogBox {
|
||||||
|
title: string;
|
||||||
|
clearLog?: () => void;
|
||||||
|
logs: ILog[];
|
||||||
|
renderNav?: () => ReactNode;
|
||||||
|
enhanced?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LogBox: FC<ILogBox> = ({
|
||||||
|
title,
|
||||||
|
clearLog,
|
||||||
|
logs,
|
||||||
|
children,
|
||||||
|
renderNav,
|
||||||
|
enhanced,
|
||||||
|
}) => {
|
||||||
|
const logRef = useRef<HTMLPreElement>(null);
|
||||||
|
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef);
|
||||||
|
const snap = useSnapshot(state);
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
stayScrolled();
|
||||||
|
}, [stayScrolled, logs]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
as="div"
|
||||||
|
css={{
|
||||||
|
display: "flex",
|
||||||
|
borderTop: "1px solid $mauve6",
|
||||||
|
background: "$mauve1",
|
||||||
|
position: "relative",
|
||||||
|
flex: 1,
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Container
|
||||||
|
css={{
|
||||||
|
px: 0,
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
fluid
|
||||||
|
css={{
|
||||||
|
height: "48px",
|
||||||
|
alignItems: "center",
|
||||||
|
fontSize: "$sm",
|
||||||
|
fontWeight: 300,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Heading
|
||||||
|
as="h3"
|
||||||
|
css={{
|
||||||
|
fontWeight: 300,
|
||||||
|
m: 0,
|
||||||
|
fontSize: "11px",
|
||||||
|
color: "$mauve12",
|
||||||
|
px: "$3",
|
||||||
|
textTransform: "uppercase",
|
||||||
|
alignItems: "center",
|
||||||
|
display: "inline-flex",
|
||||||
|
gap: "$3",
|
||||||
|
mr: "$3",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FileJs size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text>
|
||||||
|
</Heading>
|
||||||
|
<Flex css={{ gap: "$3" }}>
|
||||||
|
{snap.files
|
||||||
|
.filter((f) => f.name.endsWith(".js"))
|
||||||
|
.map((file) => (
|
||||||
|
<RunScript file={file} key={file.name} />
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
|
||||||
|
{clearLog && (
|
||||||
|
<Button ghost size="xs" onClick={clearLog}>
|
||||||
|
<Prohibit size="14px" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Box
|
||||||
|
as="pre"
|
||||||
|
ref={logRef}
|
||||||
|
css={{
|
||||||
|
margin: 0,
|
||||||
|
// display: "inline-block",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
height: "calc(100% - 48px)", // 100% minus the logbox header height
|
||||||
|
overflowY: "auto",
|
||||||
|
fontSize: "13px",
|
||||||
|
fontWeight: "$body",
|
||||||
|
fontFamily: "$monospace",
|
||||||
|
px: "$3",
|
||||||
|
pb: "$2",
|
||||||
|
whiteSpace: "normal",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{logs?.map((log, index) => (
|
||||||
|
<Box
|
||||||
|
as="span"
|
||||||
|
key={log.type + index}
|
||||||
|
css={{
|
||||||
|
"@hover": {
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: enhanced ? "$backgroundAlt" : undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
p: enhanced ? "$1" : undefined,
|
||||||
|
my: enhanced ? "$1" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Log {...log} />
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Log: FC<ILog> = ({
|
||||||
|
type,
|
||||||
|
timestring,
|
||||||
|
message: _message,
|
||||||
|
link,
|
||||||
|
linkText,
|
||||||
|
defaultCollapsed,
|
||||||
|
jsonData: _jsonData,
|
||||||
|
}) => {
|
||||||
|
const [expanded, setExpanded] = useState(!defaultCollapsed);
|
||||||
|
const { accounts } = useSnapshot(state);
|
||||||
|
const [dialogAccount, setDialogAccount] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const enrichAccounts = useCallback(
|
||||||
|
(str?: string): ReactNode => {
|
||||||
|
if (!str || !accounts.length) return null;
|
||||||
|
|
||||||
|
const pattern = `(${accounts.map((acc) => acc.address).join("|")})`;
|
||||||
|
const res = regexifyString({
|
||||||
|
pattern: new RegExp(pattern, "gim"),
|
||||||
|
decorator: (match, idx) => {
|
||||||
|
const name = accounts.find((acc) => acc.address === match)?.name;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={match + idx}
|
||||||
|
as="a"
|
||||||
|
onClick={() => setDialogAccount(match)}
|
||||||
|
title={match}
|
||||||
|
highlighted
|
||||||
|
>
|
||||||
|
{name || match}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
input: str,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <>{res}</>;
|
||||||
|
},
|
||||||
|
[accounts]
|
||||||
|
);
|
||||||
|
|
||||||
|
let message: ReactNode;
|
||||||
|
|
||||||
|
if (typeof _message === "string") {
|
||||||
|
_message = _message.trim().replace(/\n /gi, "\n");
|
||||||
|
message = enrichAccounts(_message);
|
||||||
|
} else {
|
||||||
|
message = _message;
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonData = enrichAccounts(_jsonData);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AccountDialog
|
||||||
|
setActiveAccountAddress={setDialogAccount}
|
||||||
|
activeAccountAddress={dialogAccount}
|
||||||
|
/>
|
||||||
|
<LogText variant={type}>
|
||||||
|
{timestring && (
|
||||||
|
<Text muted monospace>
|
||||||
|
{timestring}{" "}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Pre>{message} </Pre>
|
||||||
|
{link && (
|
||||||
|
<NextLink href={link} shallow passHref>
|
||||||
|
<Link as="a">{linkText}</Link>
|
||||||
|
</NextLink>
|
||||||
|
)}
|
||||||
|
{jsonData && (
|
||||||
|
<Link onClick={() => setExpanded(!expanded)} as="a">
|
||||||
|
{expanded ? "Collapse" : "Expand"}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
{expanded && jsonData && <Pre block>{jsonData}</Pre>}
|
||||||
|
</LogText>
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LogBox;
|
||||||
169
components/RunScript/index.tsx
Normal file
169
components/RunScript/index.tsx
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import 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";
|
||||||
|
|
||||||
|
const generateHtmlTemplate = (code: string) => {
|
||||||
|
return `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script>
|
||||||
|
function log() {
|
||||||
|
var args = Array.from(arguments);
|
||||||
|
this.parent.window.postMessage({ type: 'log', args: args || [] }, '*');
|
||||||
|
};
|
||||||
|
function error() {
|
||||||
|
var args = Array.from(arguments);
|
||||||
|
this.parent.window.postMessage({ type: 'error', args: args || [] }, '*');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script type="module">
|
||||||
|
log('Started running...');
|
||||||
|
${code}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
kissa
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
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")
|
||||||
|
// @ts-expect-error
|
||||||
|
.map((block) => block?.path?.original);
|
||||||
|
const defaultState: Record<string, string> = {};
|
||||||
|
names.forEach((name) => (defaultState[name] = ""));
|
||||||
|
const [fields, setFields] = useState<Record<string, string>>(defaultState);
|
||||||
|
const [iFrameCode, setIframeCode] = useState("");
|
||||||
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
|
const runScript = () => {
|
||||||
|
const template = Handlebars.compile(file.content);
|
||||||
|
const code = template(fields);
|
||||||
|
setIframeCode(generateHtmlTemplate(code));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleEvent = (e: any) => {
|
||||||
|
if (e.data.type === "log" || e.data.type === "error") {
|
||||||
|
console.log(e.data);
|
||||||
|
const data: ILog[] = e.data.args.map((msg: any) => ({
|
||||||
|
type: e.data.type,
|
||||||
|
message: msg.toString(),
|
||||||
|
}));
|
||||||
|
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>{key}</label>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={fields[key]}
|
||||||
|
css={{ mt: "$1" }}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFields({ ...fields, [key]: 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]: [string, string]) => !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 allow-same-origin"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RunScript;
|
||||||
@@ -6,6 +6,7 @@ import Transaction from "../../components/Transaction";
|
|||||||
import state from "../../state";
|
import state from "../../state";
|
||||||
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
|
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
|
||||||
import { transactionsState, modifyTransaction } from "../../state";
|
import { transactionsState, modifyTransaction } from "../../state";
|
||||||
|
import LogBoxForScripts from "../../components/LogBoxForScripts";
|
||||||
|
|
||||||
const DebugStream = dynamic(() => import("../../components/DebugStream"), {
|
const DebugStream = dynamic(() => import("../../components/DebugStream"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@@ -21,16 +22,16 @@ const Accounts = dynamic(() => import("../../components/Accounts"), {
|
|||||||
const Test = () => {
|
const Test = () => {
|
||||||
const { transactionLogs } = useSnapshot(state);
|
const { transactionLogs } = useSnapshot(state);
|
||||||
const { transactions, activeHeader } = useSnapshot(transactionsState);
|
const { transactions, activeHeader } = useSnapshot(transactionsState);
|
||||||
|
const snap = useSnapshot(state);
|
||||||
return (
|
return (
|
||||||
<Container css={{ px: 0 }}>
|
<Container css={{ px: 0 }}>
|
||||||
<Split
|
<Split
|
||||||
direction="vertical"
|
direction="vertical"
|
||||||
sizes={getSplit("testVertical") || [50, 50]}
|
sizes={getSplit("testVertical") || [50, 20, 30]}
|
||||||
gutterSize={4}
|
gutterSize={4}
|
||||||
gutterAlign="center"
|
gutterAlign="center"
|
||||||
style={{ height: "calc(100vh - 60px)" }}
|
style={{ height: "calc(100vh - 60px)" }}
|
||||||
onDragEnd={e => saveSplit("testVertical", e)}
|
onDragEnd={(e) => saveSplit("testVertical", e)}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
row
|
row
|
||||||
@@ -52,7 +53,7 @@ const Test = () => {
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
}}
|
}}
|
||||||
onDragEnd={e => saveSplit("testHorizontal", e)}
|
onDragEnd={(e) => saveSplit("testHorizontal", e)}
|
||||||
>
|
>
|
||||||
<Box css={{ width: "55%", px: "$2" }}>
|
<Box css={{ width: "55%", px: "$2" }}>
|
||||||
<Tabs
|
<Tabs
|
||||||
@@ -64,17 +65,14 @@ const Test = () => {
|
|||||||
keepAllAlive
|
keepAllAlive
|
||||||
forceDefaultExtension
|
forceDefaultExtension
|
||||||
defaultExtension=".json"
|
defaultExtension=".json"
|
||||||
onCreateNewTab={header => modifyTransaction(header, {})}
|
onCreateNewTab={(header) => modifyTransaction(header, {})}
|
||||||
onCloseTab={(idx, header) =>
|
onCloseTab={(idx, header) =>
|
||||||
header && modifyTransaction(header, undefined)
|
header && modifyTransaction(header, undefined)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{transactions.map(({ header, state }) => (
|
{transactions.map(({ header, state }) => (
|
||||||
<Tab key={header} header={header}>
|
<Tab key={header} header={header}>
|
||||||
<Transaction
|
<Transaction state={state} header={header} />
|
||||||
state={state}
|
|
||||||
header={header}
|
|
||||||
/>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@@ -84,8 +82,17 @@ const Test = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Split>
|
</Split>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Flex
|
||||||
<Flex row fluid>
|
as="div"
|
||||||
|
css={{
|
||||||
|
borderTop: "1px solid $mauve6",
|
||||||
|
background: "$mauve1",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LogBoxForScripts title="Helper scripts" logs={snap.scriptLogs} />
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
<Split
|
<Split
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
sizes={[50, 50]}
|
sizes={[50, 50]}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export interface IState {
|
|||||||
logs: ILog[];
|
logs: ILog[];
|
||||||
deployLogs: ILog[];
|
deployLogs: ILog[];
|
||||||
transactionLogs: ILog[];
|
transactionLogs: ILog[];
|
||||||
|
scriptLogs: ILog[];
|
||||||
editorCtx?: typeof monaco.editor;
|
editorCtx?: typeof monaco.editor;
|
||||||
editorSettings: {
|
editorSettings: {
|
||||||
tabSize: number;
|
tabSize: number;
|
||||||
@@ -96,6 +97,7 @@ let initialState: IState = {
|
|||||||
logs: [],
|
logs: [],
|
||||||
deployLogs: [],
|
deployLogs: [],
|
||||||
transactionLogs: [],
|
transactionLogs: [],
|
||||||
|
scriptLogs: [],
|
||||||
editorCtx: undefined,
|
editorCtx: undefined,
|
||||||
gistId: undefined,
|
gistId: undefined,
|
||||||
gistOwner: undefined,
|
gistOwner: undefined,
|
||||||
|
|||||||
Reference in New Issue
Block a user