Skip to content

Commit fe782ad

Browse files
React output in playground (#875)
* render react * fix jsx runtime import * put back initial content * fix App render * add instructions * improve log output a bit * use padding instead of margin for render panel * stack the react and console panels * Adapt OutputPanel layout * Make eval less evil --------- Co-authored-by: Paul Tsnobiladzé <[email protected]>
1 parent 5d27e1c commit fe782ad

File tree

6 files changed

+99
-120
lines changed

6 files changed

+99
-120
lines changed

src/ConsolePanel.res

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ type logLevel = [
55
]
66

77
@react.component
8-
let make = (~compilerState, ~runOutput) => {
9-
let (logs, setLogs) = React.useState(_ => [])
10-
8+
let make = (~logs, ~setLogs) => {
119
React.useEffect(() => {
1210
let cb = e => {
1311
let data = e["data"]
@@ -22,23 +20,13 @@ let make = (~compilerState, ~runOutput) => {
2220
Some(() => Webapi.Window.removeEventListener("message", cb))
2321
}, [])
2422

25-
React.useEffect(() => {
26-
if runOutput {
27-
switch compilerState {
28-
| CompilerManagerHook.Ready({result: Comp(Success({js_code}))}) =>
29-
setLogs(_ => [])
30-
let ast = AcornParse.parse(js_code)
31-
let transpiled = AcornParse.removeImportsAndExports(ast)
32-
EvalIFrame.sendOutput(transpiled)
33-
| _ => ()
34-
}
35-
}
36-
None
37-
}, (compilerState, runOutput))
38-
39-
<div>
23+
<div className="px-2 py-6 relative flex flex-col flex-1 overflow-y-hidden">
24+
<h2 className="font-bold text-gray-5/50 absolute right-2 top-2"> {React.string("Console")} </h2>
4025
{switch logs {
41-
| [] => React.null
26+
| [] =>
27+
React.string(
28+
"Add some 'Console.log' to your code and enable 'Auto-run' to see your logs here.",
29+
)
4230
| logs =>
4331
let content =
4432
logs
@@ -56,8 +44,7 @@ let make = (~compilerState, ~runOutput) => {
5644
})
5745
->React.array
5846

59-
<div className="whitespace-pre-wrap p-4 block"> content </div>
47+
<div className="whitespace-pre-wrap p-4 overflow-auto"> content </div>
6048
}}
61-
<EvalIFrame />
6249
</div>
6350
}

src/OutputPanel.res

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@react.component
2+
let make = (~runOutput, ~compilerState, ~logs, ~setLogs) => {
3+
<div className="h-full flex flex-col overflow-y-hidden">
4+
<RenderPanel runOutput compilerState clearLogs={() => setLogs(_ => [])} />
5+
<hr className="border-gray-60" />
6+
<ConsolePanel logs setLogs />
7+
</div>
8+
}

src/Playground.res

Lines changed: 6 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ open CompilerManagerHook
2222
module Api = RescriptCompilerApi
2323

2424
type layout = Column | Row
25-
type tab = JavaScript | Problems | Settings | Console
25+
type tab = JavaScript | Output | Problems | Settings
2626
let breakingPoint = 1024
2727

2828
module DropdownSelect = {
@@ -1204,67 +1204,6 @@ let locMsgToCmError = (~kind: CodeMirror.Error.kind, locMsg: Api.LocMsg.t): Code
12041204
}
12051205
}
12061206

1207-
// module RenderOutput = {
1208-
// @react.component
1209-
// let make = (~compilerState: CompilerManagerHook.state) => {
1210-
// React.useEffect(() => {
1211-
// let code = switch compilerState {
1212-
// | Ready(ready) =>
1213-
// switch ready.result {
1214-
// | Comp(Success(_)) => ControlPanel.codeFromResult(ready.result)->Some
1215-
// | _ => None
1216-
// }
1217-
// | _ => None
1218-
// }
1219-
1220-
// let _valid = switch code {
1221-
// | Some(code) =>
1222-
// switch RenderOutputManager.renderOutput(code) {
1223-
// | Ok(_) => true
1224-
// | Error(_) => false
1225-
// }
1226-
// | None => false
1227-
// }
1228-
// None
1229-
// }, [compilerState])
1230-
1231-
// <div className={""}>
1232-
// <iframe
1233-
// width="100%"
1234-
// id="iframe-eval"
1235-
// className="relative w-full text-gray-20"
1236-
// srcDoc=RenderOutputManager.Frame.srcdoc
1237-
// />
1238-
// </div>
1239-
1240-
// // switch code {
1241-
// // | Some(code) =>
1242-
// // switch RenderOutputManager.renderOutput(code) {
1243-
// // | Ok() =>
1244-
// // <iframe
1245-
// // width="100%"
1246-
// // id="iframe-eval"
1247-
// // className="relative w-full text-gray-20"
1248-
// // srcDoc=RenderOutputManager.Frame.srcdoc
1249-
// // />
1250-
// // | Error() =>
1251-
// // let code = `module App = {
1252-
// // @react.component
1253-
// // let make = () => {
1254-
// // <ModuleName />
1255-
// // }
1256-
// // }`
1257-
// // <div className={"whitespace-pre-wrap p-4 block"}>
1258-
// // <p className={"mb-2"}> {React.string("To render element create a module App")} </p>
1259-
// // <pre> {HighlightJs.renderHLJS(~code, ~darkmode=true, ~lang="rescript", ())} </pre>
1260-
// // </div>
1261-
// // }
1262-
1263-
// // | _ => React.null
1264-
// // }
1265-
// }
1266-
// }
1267-
12681207
module OutputPanel = {
12691208
@react.component
12701209
let make = (
@@ -1381,8 +1320,10 @@ module OutputPanel = {
13811320

13821321
prevSelected.current = selected
13831322

1323+
let (logs, setLogs) = React.useState(_ => [])
1324+
13841325
let tabs = [
1385-
(Console, <ConsolePanel compilerState runOutput />),
1326+
(Output, <OutputPanel runOutput compilerState logs setLogs />),
13861327
(JavaScript, output),
13871328
(Problems, errorPane),
13881329
(Settings, settingsPane),
@@ -1763,12 +1704,11 @@ let make = (~versions: array<string>) => {
17631704
"flex-1 items-center p-4 border-t-4 border-transparent " ++ activeClass
17641705
}
17651706

1766-
let tabs = [JavaScript, Console, Problems, Settings]
1707+
let tabs = [JavaScript, Output, Problems, Settings]
17671708

17681709
let headers = Array.mapWithIndex(tabs, (tab, i) => {
17691710
let title = switch tab {
1770-
// | RenderOutput => "Render Output"
1771-
| Console => "Console"
1711+
| Output => "Output"
17721712
| JavaScript => "JavaScript"
17731713
| Problems => "Problems"
17741714
| Settings => "Settings"

src/RenderPanel.res

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
let wrapReactApp = code =>
2+
`(function () {
3+
${code}
4+
window.reactRoot.render(React.createElement(App.make, {}));
5+
})();`
6+
7+
@react.component
8+
let make = (~compilerState: CompilerManagerHook.state, ~clearLogs, ~runOutput) => {
9+
let (validReact, setValidReact) = React.useState(() => false)
10+
React.useEffect(() => {
11+
if runOutput {
12+
switch compilerState {
13+
| CompilerManagerHook.Ready({result: Comp(Success({js_code}))}) =>
14+
clearLogs()
15+
let ast = AcornParse.parse(js_code)
16+
let transpiled = AcornParse.removeImportsAndExports(ast)
17+
let isValidReact = AcornParse.hasEntryPoint(ast)
18+
isValidReact
19+
? transpiled->wrapReactApp->EvalIFrame.sendOutput
20+
: EvalIFrame.sendOutput(transpiled)
21+
setValidReact(_ => isValidReact)
22+
| _ => ()
23+
}
24+
}
25+
None
26+
}, (compilerState, runOutput))
27+
28+
<div className={`px-2 relative ${validReact ? "flex-1 py-2 overflow-y-auto" : "h-auto py-6"}`}>
29+
<h2 className="font-bold text-gray-5/50 absolute right-2 top-2"> {React.string("React")} </h2>
30+
{validReact
31+
? React.null
32+
: React.string(
33+
"Create a React component called 'App' if you want to render it here, then enable 'Auto-run'.",
34+
)}
35+
<div className={validReact ? "h-full" : "h-0"}>
36+
<EvalIFrame />
37+
</div>
38+
</div>
39+
}

src/common/EvalIFrame.res

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ let css = `body {
44
color-scheme: light dark;
55
}`
66

7+
let reactVersion = "18.2.0"
8+
79
let srcDoc = `
810
<html>
911
<head>
@@ -13,22 +15,49 @@ let srcDoc = `
1315
</head>
1416
<body>
1517
<div id="root"></div>
18+
<script type="importmap">
19+
{
20+
"imports": {
21+
"@jsxImportSource": "https://esm.sh/react@${reactVersion}",
22+
"react-dom/client": "https://esm.sh/react-dom@${reactVersion}/client",
23+
"react": "https://esm.sh/react@${reactVersion}",
24+
"react/jsx-runtime": "https://esm.sh/react@${reactVersion}/jsx-runtime"
25+
}
26+
}
27+
</script>
28+
<script type="module">
29+
import * as ReactDOM from 'react-dom/client';
30+
import * as React from 'react';
31+
import * as JsxRuntime from 'react/jsx-runtime';
32+
const container = document.getElementById("root");
33+
const root = ReactDOM.createRoot(container);
34+
window.reactRoot = root;
35+
window.React = React;
36+
window.JsxRuntime = JsxRuntime;
37+
</script>
1638
<script>
1739
window.addEventListener("message", (event) => {
1840
try {
19-
eval(event.data);
41+
// https://rollupjs.org/troubleshooting/#avoiding-eval
42+
const eval2 = eval;
43+
eval2(event.data);
2044
} catch (err) {
2145
console.error(err);
2246
}
2347
});
2448
const sendLog = (logLevel) => (...args) => {
2549
let finalArgs = args.map(arg => {
26-
if (typeof arg === 'object') {
27-
return JSON.stringify(arg);
50+
if (arg === undefined) {
51+
return 'undefined';
52+
}
53+
else if (typeof arg === 'object') {
54+
return JSON.stringify(arg, Object.getOwnPropertyNames(arg));
55+
} else if (typeof arg === 'function') {
56+
return '[function]';
2857
}
2958
return arg;
3059
});
31-
parent.window.postMessage({ type: logLevel, args: finalArgs }, '*')
60+
parent.window.postMessage({ type: logLevel, args: finalArgs }, '*');
3261
};
3362
console.log = sendLog('log');
3463
console.warn = sendLog('warn');
@@ -41,22 +70,19 @@ let srcDoc = `
4170
let sendOutput = code => {
4271
open Webapi
4372

44-
let frame =
45-
Document.document
46-
->Element.getElementById("iframe-eval")
47-
->Nullable.toOption
73+
let frame = Document.document->Element.getElementById("iframe-eval")
4874

4975
switch frame {
50-
| Some(element) =>
76+
| Value(element) =>
5177
switch element->Element.contentWindow {
5278
| Some(win) => win->Element.postMessage(code, ~targetOrigin="*")
53-
| None => ()
79+
| None => RescriptCore.Console.error("contentWindow not found")
5480
}
55-
| None => ()
81+
| Null | Undefined => RescriptCore.Console.error("iframe not found")
5682
}
5783
}
5884

5985
@react.component
6086
let make = () => {
61-
<iframe width="100%" id="iframe-eval" className="relative w-full text-gray-20" srcDoc />
87+
<iframe width="100%" id="iframe-eval" className="relative h-full w-full text-gray-20" srcDoc />
6288
}

src/common/RenderOutputManager.res

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)