26
loading...
This website collects cookies to deliver better user experience
create-react-app
plus a few commands to setup React+Electron:$ npx create-react-app episode-53-jupyter-like-notebook --use-npm --template ready
$ cd episode-53-jupyter-like-notebook
$ npm i
$ npm i --save-dev electron
package.json
so React doesn't start a browser for us:"start": "BROWSER=none react-scripts start",
let { app, BrowserWindow } = require("electron")
function createWindow() {
let win = new BrowserWindow({
webPreferences: {
preload: `${__dirname}/preload.js`,
},
})
win.maximize()
win.loadURL("http://localhost:3000/")
}
app.on("ready", createWindow)
app.on("window-all-closed", () => {
app.quit()
})
Promise
. We can't easily get away with just async/await
here.let child_process = require("child_process")
let { contextBridge } = require("electron")
let runScript = (interpretter, code) => {
return new Promise((resolve, reject) => {
let output = ""
let proc = child_process.spawn(
interpretter,
[],
{
shell: true,
stdio: ["pipe", "pipe", "pipe"],
},
)
proc.stdout.on("data", (data) => output += data.toString())
proc.stderr.on("data", (data) => output += data.toString())
proc.stdin.write(code)
proc.stdin.end()
proc.on("close", () => resolve(output))
})
}
contextBridge.exposeInMainWorld(
"api", { runScript }
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Notebook App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
import React from "react"
import ReactDOM from "react-dom"
import "./index.css"
import App from "./App"
ReactDOM.render(<App />, document.getElementById("root"))
App
component will soon need to manage multiple input/output boxes, but for now it's just one, so it doesn't have any logic yet:import React from "react"
import PythonCommand from "./PythonCommand.js"
export default (props) => {
return (
<>
<h1>Notebook App</h1>
<PythonCommand />
</>
)
}
await window.api.runScript("python3", input)
. This is not what Jupyter Notebook actually does - for slow running commands it will stream the output as it happens - but it's good enough for now.
import React from "react"
export default () => {
let example = `name = "world"\nprint(f"Hello, {name}!")\n`
let [input, setInput] = React.useState(example)
let [output, setOutput] = React.useState("")
let submit = async () => {
setOutput(await window.api.runScript("python3", input))
}
let handleKey = (e) => {
if (e.key === "Enter" && e.metaKey) {
submit()
}
}
return (
<div className="command">
<textarea
className="input"
onChange={e => setInput(e.target.value)} value={input}
onKeyDown={handleKey}
/>
<div className="output">{output}</div>
</div>
)
}
body {
background-color: #444;
color: #fff;
font-family: monospace;
}
.command {
width: 80em;
}
.command textarea {
min-height: 5em;
width: 100%;
background-color: #666;
color: #fff;
font: inherit;
border: none;
padding: 4px;
margin: 0;
}
.command .output {
width: 100%;
min-height: 5em;
background-color: #666;
padding: 4px;
}
python3
interpreter with the name of the interpreter you want to use like ruby
, perl
, or even node
. As long as it accepts code on standard input.