Skip to content

Commit 34237ec

Browse files
committed
Add functions to js make it easier to add functions to python
1 parent bdcdcb1 commit 34237ec

File tree

6 files changed

+130
-148
lines changed

6 files changed

+130
-148
lines changed

wasm/notebook/snippets/python-js-css/css.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ h1 {
88

99
h2, h3, h4, button {
1010
font-family: 'Turret Road', cursive;
11-
padding-top: 1.25rem;
1211
}
1312

1413
h1 {
1514
font-size: 1.5rem;
1615
}
16+
h2, h3, h4 {
17+
padding-top: 1.25rem;
18+
}
1719

1820
input[type='text'] {
1921
border: 1px solid black;
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,25 @@
1-
// Javascript code go here
2-
3-
// Javascript code go here
4-
5-
function create_ui() {
6-
const input = document.createElement('input');
7-
input.type = 'text';
8-
input.id = 'input-1';
9-
document.getElementById('rp-notebook').appendChild(input);
10-
11-
const btn = document.createElement('button');
12-
btn.type = 'button';
13-
btn.id = 'btn-caclulate';
14-
btn.innerHTML = 'click me';
15-
btn.addEventListener('click', calculate, false);
16-
document.getElementById('rp-notebook').appendChild(btn);
17-
18-
const div = document.createElement('div');
19-
div.id = 'result';
20-
div.classList.add('result');
21-
document.getElementById('rp-notebook').appendChild(div);
22-
}
23-
24-
function calculate() {
25-
let target = document.getElementById('result');
26-
target.innerHTML = '';
27-
28-
let code = 'runModel()';
29-
30-
let input = document.getElementById('input-1').value;
31-
32-
let params = { data: input };
33-
34-
rp.pyExec(code, {
35-
stdout: (output) => {
36-
target.innerHTML += output;
37-
},
38-
vars: params,
39-
});
40-
}
1+
// Javascript code goes here
2+
3+
injectPython({
4+
// injectPython functions take the positional arguments as
5+
// normal function args, and kwargs as the `this` variable
6+
add_text_input(buttonText, callback) {
7+
const input = document.createElement('input');
8+
pushNotebook(input);
9+
10+
const btn = document.createElement('button');
11+
btn.innerHTML = buttonText;
12+
btn.addEventListener('click', () => {
13+
resultDiv.innerHTML = '';
14+
// python functions passed to js have a signature
15+
// of ([args...], {kwargs...}) => any
16+
const output = callback([input.value], {});
17+
resultDiv.innerHTML += output;
18+
});
19+
pushNotebook(btn);
20+
21+
const resultDiv = document.createElement('div');
22+
resultDiv.classList.add('result');
23+
pushNotebook(resultDiv);
24+
},
25+
});

wasm/notebook/snippets/python-js-css/main.txt

+13-14
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,20 @@ In this demo example, the RustPython Notebook:
2424

2525
---
2626

27-
## Calculator
28-
### Enter your lucky number
29-
30-
%%js
27+
%%py
3128

32-
create_ui();
29+
# Python code
3330

31+
# you have access to helpers for emitting p & h1-h6
32+
h2("Calculator")
33+
h3("Enter your lucky number")
3434

35-
%%py
36-
# Python code
35+
def runModel(input):
36+
output = f"""\
37+
<br>
38+
A little javascript birdie told the python snake your lucky number 🙊
39+
Now everyone knows that it is: {input}
40+
"""
41+
return output
3742

38-
def runModel():
39-
input = js_vars['data']
40-
print ("<br>")
41-
print("A little javascript birdie told the python snake your lucky number 🙊")
42-
print ("Now the everyone knows that it is: " + str(input))
43-
44-
43+
add_text_input("click me", runModel)

wasm/notebook/snippets/python-js.txt

+39-65
Original file line numberDiff line numberDiff line change
@@ -38,74 +38,48 @@ Here is a run down of what is happening with this demo. The notebook:
3838
- everything is styled with css, neatly.
3939
- everything runs in the browser, even on your phone, no servers.
4040

41+
%%js
4142

42-
%%py
43-
# Python code
44-
45-
def runModel():
46-
input = js_vars['data']
47-
print ("<br>")
48-
print("A little javascript birdie told the python snake your lucky number 🙊")
49-
print ("Now the everyone knows that it is: " + str(input))
50-
51-
%%md
52-
## Calculator
53-
### Enter your lucky number
43+
// Javascript code goes here
44+
45+
injectPython({
46+
// injectPython functions take the positional arguments as
47+
// normal function args, and kwargs as the `this` variable
48+
add_text_input(buttonText, callback) {
49+
const input = document.createElement('input');
50+
pushNotebook(input);
51+
52+
const btn = document.createElement('button');
53+
btn.innerHTML = buttonText;
54+
btn.addEventListener('click', () => {
55+
resultDiv.innerHTML = '';
56+
// python functions passed to js have a signature
57+
// of ([args...], {kwargs...}) => any
58+
const output = callback([input.value], {});
59+
resultDiv.innerHTML += output;
60+
});
61+
pushNotebook(btn);
62+
63+
const resultDiv = document.createElement('div');
64+
resultDiv.classList.add('result');
65+
pushNotebook(resultDiv);
66+
},
67+
});
5468

55-
%%js
69+
%%py
5670

57-
// Javascript code go here
58-
59-
function create_ui() {
60-
61-
const input = document.createElement("input");
62-
input.type = "text";
63-
input.id = "input-1";
64-
document.getElementById("rp-notebook").appendChild(input);
65-
66-
const btn = document.createElement("button");
67-
btn.type = "button";
68-
btn.id = "btn-caclulate";
69-
btn.innerHTML = "click me";
70-
btn.addEventListener('click', calculate, false);
71-
document.getElementById("rp-notebook").appendChild(btn);
72-
73-
const div = document.createElement("div");
74-
div.id = "result";
75-
div.classList.add("result");
76-
document.getElementById("rp-notebook").appendChild(div);
77-
78-
}
79-
80-
function calculate() {
81-
82-
let target = document.getElementById("result");
83-
target.innerHTML = "";
84-
85-
let code = 'runModel()';
86-
87-
let input = document.getElementById("input-1").value;
88-
89-
let params = { "data": input };
90-
91-
92-
rp.pyExec(code, {
93-
stdout: (output) => {
94-
target.innerHTML += output;
95-
},
96-
vars: params
97-
});
98-
99-
}
100-
101-
create_ui();
71+
# Python code goes here
10272

73+
# you have access to helpers for emitting p & h1-h6
74+
h2("Calculator")
75+
h3("Enter your lucky number")
10376

104-
%%py
105-
# Python code
77+
def runModel(input):
78+
output = f"""\
79+
<br>
80+
A little javascript birdie told the python snake your lucky number 🙊
81+
Now everyone knows that it is: {input}
82+
"""
83+
return output
10684

107-
def runModel():
108-
input = js_vars['data']
109-
print ("<br>")
110-
print("A little javascript birdie told the python snake your lucky number 🙊")
111-
print ("Now the everyone knows that it is: " + str(input))
85+
add_text_input("click me", runModel)

wasm/notebook/src/index.js

+43-21
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ openBuffer(
102102
openBuffer(
103103
buffers,
104104
'js',
105-
'// Javascript code go here',
105+
'// Javascript code goes here',
106106
'javascript',
107107
buffersDropDown,
108108
buffersList
@@ -136,13 +136,15 @@ function onReady() {
136136
notebook.innerHTML = '';
137137
}
138138

139-
document.getElementById('run-btn').addEventListener('click', readEditors);
139+
document.getElementById('run-btn').addEventListener('click', executeNotebook);
140+
141+
let pyvm = null;
140142

141143
// on click of run
142144
// 1. add css stylesheet
143145
// 2. get and run content of all tabs (including dynamically added ones)
144146
// 3. run main tab.
145-
function readEditors() {
147+
async function executeNotebook() {
146148
// Clean the console and errors
147149
notebook.innerHTML = '';
148150
error.textContent = '';
@@ -165,9 +167,39 @@ function readEditors() {
165167
// do nothing
166168
}
167169

168-
//
170+
if (pyvm) {
171+
pyvm.destroy();
172+
pyvm = null;
173+
}
174+
pyvm = rp.vmStore.init('notebook_vm');
175+
176+
// add some helpers for js/python code
177+
window.injectPython = (ns) => {
178+
for (const [k, v] of Object.entries(ns)) {
179+
pyvm.addToScope(k, v);
180+
}
181+
};
182+
window.pushNotebook = (elem) => {
183+
notebook.appendChild(elem);
184+
};
185+
pyvm.setStdout((text) => {
186+
const para = document.createElement('p');
187+
para.appendChild(document.createTextNode(text));
188+
notebook.appendChild(para);
189+
});
190+
for (const el of ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p']) {
191+
pyvm.addToScope(el, (text) => {
192+
const elem = document.createElement(el);
193+
elem.appendChild(document.createTextNode(text));
194+
notebook.appendChild(elem);
195+
});
196+
}
197+
pyvm.addToScope('notebook_html', (html) => {
198+
notebook.innerHTML += html;
199+
});
200+
169201
let jsCode = buffers['js'].getValue();
170-
runJS(jsCode);
202+
await runJS(jsCode);
171203

172204
// get all the buffers, except css, js and main
173205
// css is auto executed at the start
@@ -177,20 +209,10 @@ function readEditors() {
177209

178210
for (const [name] of Object.entries(pythonBuffers)) {
179211
let pythonCode = buffers[name].getValue();
180-
runPython(pythonCode, notebook, error);
212+
runPython(pyvm, pythonCode, error);
181213
}
182214

183-
parseCodeFromMainEditor();
184-
}
185-
186-
// Parses what is the code editor
187-
// either runs python or renders math or markdown
188-
function parseCodeFromMainEditor() {
189-
// TODO: fix how javascript is injected and executed
190-
// Read javascript code from the jsEditor
191-
// Inject JS into DOM, so that functions can be called from python
192-
// let js_code = buffers["js"].getValue();
193-
// runJS(js_code);
215+
// now parse from the main editor
194216

195217
// gets code from main editor
196218
let mainCode = buffers['main'].getValue();
@@ -202,7 +224,7 @@ function parseCodeFromMainEditor() {
202224
- evalFlags, startLine, endLine
203225
*/
204226
let parsedCode = iomdParser(mainCode);
205-
parsedCode.forEach(async (chunk) => {
227+
for (const chunk of parsedCode) {
206228
// For each type of chunk, do somthing
207229
// so far have py for python, md for markdown and math for math ;p
208230
let content = chunk.chunkContent;
@@ -211,11 +233,11 @@ function parseCodeFromMainEditor() {
211233
// so users don't have to type py manually
212234
case '':
213235
case 'py':
214-
runPython(content, notebook, error);
236+
runPython(pyvm, content, error);
215237
break;
216238
// TODO: fix how js is injected and ran
217239
case 'js':
218-
runJS(content);
240+
await runJS(content);
219241
break;
220242
case 'md':
221243
notebook.innerHTML += renderMarkdown(content);
@@ -226,7 +248,7 @@ function parseCodeFromMainEditor() {
226248
default:
227249
// do nothing when we see an unknown chunk for now
228250
}
229-
});
251+
}
230252
}
231253

232254
function updatePopup(type, message) {

wasm/notebook/src/process.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,9 @@ function renderMath(math) {
2525
});
2626
}
2727

28-
function runPython(code, target, error) {
28+
function runPython(pyvm, code, error) {
2929
try {
30-
rp.pyExec(code, {
31-
stdout: (output) => {
32-
target.innerHTML += output;
33-
},
34-
});
30+
pyvm.exec(code);
3531
} catch (err) {
3632
if (err instanceof WebAssembly.RuntimeError) {
3733
err = window.__RUSTPYTHON_ERROR || err;
@@ -58,16 +54,20 @@ function checkCssStatus() {
5854
}
5955
}
6056

61-
function runJS(code) {
57+
async function runJS(code) {
6258
const script = document.createElement('script');
6359
const doc = document.body || document.documentElement;
6460
const blob = new Blob([code], { type: 'text/javascript' });
6561
const url = URL.createObjectURL(blob);
6662
script.src = url;
63+
const scriptLoaded = new Promise((resolve) => {
64+
script.addEventListener('load', resolve);
65+
});
6766
doc.appendChild(script);
6867
try {
6968
URL.revokeObjectURL(url);
7069
doc.removeChild(script);
70+
await scriptLoaded;
7171
} catch (e) {
7272
// ignore if body is changed and script is detached
7373
console.log(e);

0 commit comments

Comments
 (0)