Compare commits

...

14 Commits

Author SHA1 Message Date
Vaclav Barta
a82de7087d -O0 -> -O2 2022-06-22 08:47:00 +02:00
Valtteri Karesto
1a6726fabf Merge pull request #212 from XRPLF/feat/improve-supp-js
Improve supplementary JS feature
2022-06-21 11:28:42 +03:00
Valtteri Karesto
89f8671217 Clear log should now work 2022-06-21 10:53:52 +03:00
Valtteri Karesto
fb5259221b Changed color of starting running 2022-06-21 09:43:29 +03:00
Valtteri Karesto
fd17f59616 Show LogBoxForScrips if js file active 2022-06-20 23:54:56 +03:00
Valtteri Karesto
91bbc7ea61 Catch template errors, add better labels, styling 2022-06-20 23:54:33 +03:00
Valtteri Karesto
783d832c6d Remove export for unused component 2022-06-20 23:53:32 +03:00
Valtteri Karesto
698ca376e7 Add showbuttons prop to LogBoxForScripts 2022-06-20 23:53:15 +03:00
Valtteri Karesto
bfd9e21ab8 Remove unused component 2022-06-20 23:52:45 +03:00
Valtteri Karesto
e46411f245 Rename template helpers 2022-06-20 14:53:30 +03:00
Valtteri Karesto
08447c6b29 Add support for select parameters 2022-06-20 14:16:16 +03:00
Valtteri Karesto
9216cc6bf7 When downloading zip, include wasm if it exists 2022-06-20 11:01:13 +03:00
Valtteri Karesto
5108b08e39 Do not show scripts panel if no supplementary scripts 2022-06-20 10:40:47 +03:00
Valtteri Karesto
6c46a4f809 Merge pull request #211 from XRPLF/feat/user-provided-scripts
Feat/user provided scripts
2022-06-20 10:16:06 +03:00
9 changed files with 254 additions and 168 deletions

View File

@@ -1,103 +0,0 @@
import React, { useRef, useLayoutEffect } from "react";
import { useSnapshot } from "valtio";
import { Play, Prohibit } from "phosphor-react";
import useStayScrolled from "react-stay-scrolled";
import Container from "./Container";
import Box from "./Box";
import LogText from "./LogText";
import { compileCode } from "../state/actions";
import state from "../state";
import Button from "./Button";
import Heading from "./Heading";
const Footer = () => {
const snap = useSnapshot(state);
const logRef = useRef<HTMLPreElement>(null);
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef);
useLayoutEffect(() => {
stayScrolled();
}, [snap.logs, stayScrolled]);
return (
<Box
as="footer"
css={{
display: "flex",
borderTop: "1px solid $mauve6",
background: "$mauve1",
position: "relative",
}}
>
<Container css={{ py: "$3", flexShrink: 1 }}>
<Heading
as="h3"
css={{ fontWeight: 300, m: 0, fontSize: "11px", color: "$mauve9" }}
>
DEVELOPMENT LOG
</Heading>
<Button
ghost
size="xs"
css={{
position: "absolute",
right: "$3",
top: "$2",
color: "$mauve10",
}}
onClick={() => {
state.logs = [];
}}
>
<Prohibit size="14px" />
</Button>
<Box
as="pre"
ref={logRef}
css={{
display: "flex",
flexDirection: "column",
width: "100%",
height: "160px",
fontSize: "13px",
fontWeight: "$body",
fontFamily: "$monospace",
overflowY: "auto",
wordWrap: "break-word",
py: 3,
}}
>
{snap.logs?.map((log, index) => (
<Box as="span" key={log.type + index}>
<LogText capitalize variant={log.type}>
{log.type}:{" "}
</LogText>
<LogText>{log.message}</LogText>
</Box>
))}
</Box>
<Button
variant="primary"
uppercase
disabled={!snap.files.length}
isLoading={snap.compiling}
onClick={() => compileCode(snap.active)}
css={{
position: "absolute",
bottom: "$4",
left: "$4",
alignItems: "center",
display: "flex",
cursor: "pointer",
}}
>
<Play weight="bold" size="16px" />
Compile to Wasm
</Button>
</Container>
</Box>
);
};
export default Footer;

View File

@@ -25,6 +25,7 @@ interface ILogBox {
logs: ILog[]; logs: ILog[];
renderNav?: () => ReactNode; renderNav?: () => ReactNode;
enhanced?: boolean; enhanced?: boolean;
showButtons?: boolean;
} }
const LogBox: FC<ILogBox> = ({ const LogBox: FC<ILogBox> = ({
@@ -34,6 +35,7 @@ const LogBox: FC<ILogBox> = ({
children, children,
renderNav, renderNav,
enhanced, enhanced,
showButtons = true,
}) => { }) => {
const logRef = useRef<HTMLPreElement>(null); const logRef = useRef<HTMLPreElement>(null);
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef); const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef);
@@ -86,13 +88,15 @@ const LogBox: FC<ILogBox> = ({
> >
<FileJs size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text> <FileJs size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text>
</Heading> </Heading>
<Flex css={{ gap: "$3" }}> {showButtons && (
{snap.files <Flex css={{ gap: "$3" }}>
.filter((f) => f.name.endsWith(".js")) {snap.files
.map((file) => ( .filter((f) => f.name.endsWith(".js"))
<RunScript file={file} key={file.name} /> .map((file) => (
))} <RunScript file={file} key={file.name} />
</Flex> ))}
</Flex>
)}
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}> <Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
{clearLog && ( {clearLog && (
<Button ghost size="xs" onClick={clearLog}> <Button ghost size="xs" onClick={clearLog}>

View File

@@ -1,6 +1,6 @@
import Handlebars from "handlebars"; import * as Handlebars from "handlebars";
import { Play, X } from "phosphor-react"; import { Play, X } from "phosphor-react";
import { useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import state, { IFile, ILog } from "../../state"; import state, { IFile, ILog } from "../../state";
import Button from "../Button"; import Button from "../Button";
import Box from "../Box"; import Box from "../Box";
@@ -16,6 +16,15 @@ import {
} from "../Dialog"; } from "../Dialog";
import Flex from "../Flex"; import Flex from "../Flex";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import Select from "../Select";
import { saveFile } from "../../state/actions/saveFile";
Handlebars.registerHelper(
"customize_input",
function (/* dynamic arguments */) {
return new Handlebars.SafeString(arguments[0]);
}
);
const generateHtmlTemplate = (code: string) => { const generateHtmlTemplate = (code: string) => {
return ` return `
@@ -48,7 +57,7 @@ const generateHtmlTemplate = (code: string) => {
} }
</script> </script>
<script type="module"> <script type="module">
${code} ${code}
</script> </script>
</head> </head>
<body> <body>
@@ -56,22 +65,87 @@ const generateHtmlTemplate = (code: string) => {
</html> </html>
`; `;
}; };
const RunScript: React.FC<{ file: IFile }> = ({ file }) => {
type Fields = Record<
string,
{
key: string;
value: string;
label?: string;
type?: string;
attach?: "account_secret" | "account_address" | string;
}
>;
const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
const snap = useSnapshot(state); const snap = useSnapshot(state);
const parsed = Handlebars.parse(file.content); const [templateError, setTemplateError] = useState("");
const names = parsed.body const getFieldValues = useCallback(() => {
.filter((i) => i.type === "MustacheStatement") try {
// @ts-expect-error const parsed = Handlebars.parse(content);
.map((block) => block?.path?.original); const names = parsed.body
const defaultState: Record<string, string> = {}; .filter((i) => i.type === "MustacheStatement")
names.forEach((name) => (defaultState[name] = "")); .map((block) => {
const [fields, setFields] = useState<Record<string, string>>(defaultState); // @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 == "label");
const key =
// @ts-expect-error
block?.path?.original === "customize_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 = {};
if (names) {
names.forEach((field) => (defaultState[field.key] = field));
}
setTemplateError("");
return defaultState;
} catch (err) {
console.log(err);
setTemplateError("Could not parse template");
return undefined;
}
}, [content]);
// const defaultFieldValues = getFieldValues();
const [fields, setFields] = useState<Fields>({});
const [iFrameCode, setIframeCode] = useState(""); const [iFrameCode, setIframeCode] = useState("");
const [isDialogOpen, setIsDialogOpen] = useState(false); const [isDialogOpen, setIsDialogOpen] = useState(false);
const runScript = () => { const runScript = () => {
const template = Handlebars.compile(file.content); const fieldsToSend: Record<string, string> = {};
const code = template(fields); Object.entries(fields).map(([key, obj]) => {
setIframeCode(generateHtmlTemplate(code)); fieldsToSend[key] = obj.value;
});
const template = Handlebars.compile(content, { strict: false });
try {
const code = template(fieldsToSend);
setIframeCode(generateHtmlTemplate(code));
state.scriptLogs = [
...snap.scriptLogs,
{ type: "success", message: "Started running..." },
];
} catch (err) {
state.scriptLogs = [
...snap.scriptLogs,
// @ts-expect-error
{ type: "error", message: err?.message || "Could not parse template" },
];
}
}; };
useEffect(() => { useEffect(() => {
@@ -88,6 +162,18 @@ const RunScript: React.FC<{ file: IFile }> = ({ file }) => {
return () => window.removeEventListener("message", handleEvent); return () => window.removeEventListener("message", handleEvent);
}, [snap.scriptLogs]); }, [snap.scriptLogs]);
useEffect(() => {
const newDefaultState = getFieldValues();
setFields(newDefaultState || {});
}, [content, setFields, getFieldValues]);
const options = snap.accounts?.map((acc) => ({
label: acc.name,
secret: acc.secret,
address: acc.address,
value: acc.address,
}));
return ( return (
<> <>
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}> <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
@@ -95,18 +181,28 @@ const RunScript: React.FC<{ file: IFile }> = ({ file }) => {
<Button <Button
variant="primary" variant="primary"
onClick={() => { onClick={() => {
saveFile(false);
setIframeCode(""); setIframeCode("");
}} }}
> >
{file.name} <Play weight="bold" size="16px" /> <Play weight="bold" size="16px" /> {name}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<DialogTitle>Run {file.name} script</DialogTitle> <DialogTitle>Run {name} script</DialogTitle>
<DialogDescription> <DialogDescription>
You are about to run scripts provided by the developer of the hook, You are about to run scripts provided by the developer of the hook,
make sure you know what you are doing. make sure you know what you are doing.
<br /> <br />
{templateError && (
<Box
as="span"
css={{ display: "block", color: "$error", mt: "$3" }}
>
Error occured while parsing template, modify script and try
again!
</Box>
)}
<br /> <br />
{Object.keys(fields).length > 0 {Object.keys(fields).length > 0
? `You also need to fill in following parameters to run the script` ? `You also need to fill in following parameters to run the script`
@@ -115,15 +211,52 @@ const RunScript: React.FC<{ file: IFile }> = ({ file }) => {
<Stack css={{ width: "100%" }}> <Stack css={{ width: "100%" }}>
{Object.keys(fields).map((key) => ( {Object.keys(fields).map((key) => (
<Box key={key} css={{ width: "100%" }}> <Box key={key} css={{ width: "100%" }}>
<label>{key}</label> <label>
<Input {fields[key]?.label || key}{" "}
type="text" {fields[key].attach === "account_secret" &&
value={fields[key]} `(Script uses account secret)`}
css={{ mt: "$1" }} </label>
onChange={(e) => {fields[key].attach === "account_secret" ||
setFields({ ...fields, [key]: e.target.value }) fields[key].attach === "account_address" ? (
} <Select
/> css={{ mt: "$1" }}
options={options}
onChange={(val: any) => {
setFields({
...fields,
[key]: {
...fields[key],
value:
fields[key].attach === "account_secret"
? val.secret
: val.address,
},
});
}}
value={options.find(
(opt) =>
opt.address === fields[key].value ||
opt.secret === 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> </Box>
))} ))}
<Flex <Flex
@@ -135,10 +268,9 @@ const RunScript: React.FC<{ file: IFile }> = ({ file }) => {
<Button <Button
variant="primary" variant="primary"
isDisabled={ isDisabled={
Object.entries(fields).length > 0 && (Object.entries(fields).length > 0 &&
Object.entries(fields).every( Object.entries(fields).some(([key, obj]) => !obj.value)) ||
([key, value]: [string, string]) => !value Boolean(templateError)
)
} }
onClick={() => { onClick={() => {
state.scriptLogs = []; state.scriptLogs = [];

View File

@@ -12,6 +12,5 @@ export { default as Box } from "./Box";
export { default as Button } from "./Button"; export { default as Button } from "./Button";
export { default as Pre } from "./Pre"; export { default as Pre } from "./Pre";
export { default as ButtonGroup } from "./ButtonGroup"; export { default as ButtonGroup } from "./ButtonGroup";
export { default as DeployFooter } from "./DeployFooter";
export * from "./Dialog"; export * from "./Dialog";
export * from "./DropdownMenu"; export * from "./DropdownMenu";

View File

@@ -8,7 +8,9 @@ import { useSnapshot } from "valtio";
import { ButtonGroup, Flex } from "../../components"; import { ButtonGroup, Flex } from "../../components";
import Box from "../../components/Box"; import Box from "../../components/Box";
import Button from "../../components/Button"; import Button from "../../components/Button";
import LogBoxForScripts from "../../components/LogBoxForScripts";
import Popover from "../../components/Popover"; import Popover from "../../components/Popover";
import RunScript from "../../components/RunScript";
import state from "../../state"; import state from "../../state";
import { compileCode } from "../../state/actions"; import { compileCode } from "../../state/actions";
import { getSplit, saveSplit } from "../../state/actions/persistSplits"; import { getSplit, saveSplit } from "../../state/actions/persistSplits";
@@ -196,20 +198,61 @@ const Home: NextPage = () => {
</Flex> </Flex>
</Hotkeys> </Hotkeys>
)} )}
{snap.files[snap.active]?.name?.split(".")?.[1]?.toLowerCase() ===
"js" && (
<Hotkeys
keyName="command+b,ctrl+b"
onKeyDown={() =>
!snap.compiling && snap.files.length && compileCode(snap.active)
}
>
<Flex
css={{
position: "absolute",
bottom: "$4",
left: "$4",
alignItems: "center",
display: "flex",
cursor: "pointer",
gap: "$2",
}}
>
<RunScript file={snap.files[snap.active]} />
</Flex>
</Hotkeys>
)}
</main> </main>
<Box <Flex css={{ width: "100%" }}>
css={{ <Flex
display: "flex", css={{
background: "$mauve1", flex: 1,
position: "relative", background: "$mauve1",
}} position: "relative",
> borderRight: "1px solid $mauve8",
<LogBox }}
title="Development Log" >
clearLog={() => (state.logs = [])} <LogBox
logs={snap.logs} title="Development Log"
/> clearLog={() => (state.logs = [])}
</Box> logs={snap.logs}
/>
</Flex>
{snap.files[snap.active]?.name?.split(".")?.[1]?.toLowerCase() ===
"js" && (
<Flex
css={{
flex: 1,
}}
>
<LogBoxForScripts
showButtons={false}
title="Script Log"
logs={snap.scriptLogs}
clearLog={() => (state.scriptLogs = [])}
/>
</Flex>
)}
</Flex>
</Split> </Split>
); );
}; };

View File

@@ -32,11 +32,15 @@ const Test = () => {
if (!showComponent) { if (!showComponent) {
return null; return null;
} }
const hasScripts =
snap.files.filter((f) => f.name.endsWith(".js")).length > 0;
return ( return (
<Container css={{ px: 0 }}> <Container css={{ px: 0 }}>
<Split <Split
direction="vertical" direction="vertical"
sizes={getSplit("testVertical") || [50, 20, 30]} sizes={
getSplit("testVertical") || (hasScripts ? [50, 20, 30] : [50, 50])
}
gutterSize={4} gutterSize={4}
gutterAlign="center" gutterAlign="center"
style={{ height: "calc(100vh - 60px)" }} style={{ height: "calc(100vh - 60px)" }}
@@ -91,16 +95,22 @@ const Test = () => {
</Box> </Box>
</Split> </Split>
</Flex> </Flex>
<Flex {hasScripts && (
as="div" <Flex
css={{ as="div"
borderTop: "1px solid $mauve6", css={{
background: "$mauve1", borderTop: "1px solid $mauve6",
flexDirection: "column", background: "$mauve1",
}} flexDirection: "column",
> }}
<LogBoxForScripts title="Helper scripts" logs={snap.scriptLogs} /> >
</Flex> <LogBoxForScripts
title="Helper scripts"
logs={snap.scriptLogs}
clearLog={() => (state.scriptLogs = [])}
/>
</Flex>
)}
<Flex> <Flex>
<Split <Split
direction="horizontal" direction="horizontal"

View File

@@ -39,7 +39,7 @@ export const compileCode = async (activeId: number) => {
files: [ files: [
{ {
type: "c", type: "c",
options: state.compileOptions.optimizationLevel || '-O0', options: state.compileOptions.optimizationLevel || '-O2',
name: state.files[activeId].name, name: state.files[activeId].name,
src: state.files[activeId].content, src: state.files[activeId].content,
}, },

View File

@@ -8,7 +8,8 @@ export const downloadAsZip = async () => {
state.zipLoading = true state.zipLoading = true
// TODO do something about file/gist loading state // TODO do something about file/gist loading state
const files = state.files.map(({ name, content }) => ({ name, content })); const files = state.files.map(({ name, content }) => ({ name, content }));
const zipped = await createZip(files); const wasmFiles = state.files.filter(i => i.compiledContent).map(({ name, compiledContent }) => ({ name: `${name}.wasm`, content: compiledContent }));
const zipped = await createZip([...files, ...wasmFiles]);
const zipFileName = guessZipFileName(files); const zipFileName = guessZipFileName(files);
zipped.saveFile(zipFileName); zipped.saveFile(zipFileName);
} catch (error) { } catch (error) {

View File

@@ -114,7 +114,7 @@ let initialState: IState = {
mainModalShowed: false, mainModalShowed: false,
accounts: [], accounts: [],
compileOptions: { compileOptions: {
optimizationLevel: '-O0', optimizationLevel: '-O2',
strip: true strip: true
} }
}; };