Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 00060b2

Browse files
committed
add py-repl and scripts queue to stores
1 parent c584661 commit 00060b2

File tree

3 files changed

+269
-4
lines changed

3 files changed

+269
-4
lines changed

pyscriptjs/src/components/pyrepl.ts

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import {EditorState, EditorView, basicSetup} from "@codemirror/basic-setup"
2+
import { python } from "@codemirror/lang-python"
3+
// @ts-ignore
4+
import { StateCommand, Compartment } from '@codemirror/state';
5+
import { keymap, ViewUpdate } from "@codemirror/view";
6+
import { defaultKeymap } from "@codemirror/commands";
7+
import { oneDarkTheme } from "@codemirror/theme-one-dark";
8+
9+
import { pyodideLoaded, loadedEnvironments, componentDetailsNavOpen, currentComponentDetails, mode, addToScriptsQueue } from '../stores';
10+
import { addClasses } from '../utils';
11+
12+
// Premise used to connect to the first available pyodide interpreter
13+
let pyodideReadyPromise;
14+
let environments;
15+
let currentMode;
16+
17+
pyodideLoaded.subscribe(value => {
18+
pyodideReadyPromise = value;
19+
});
20+
loadedEnvironments.subscribe(value => {
21+
environments = value;
22+
});
23+
24+
let propertiesNavOpen;
25+
componentDetailsNavOpen.subscribe(value => {
26+
propertiesNavOpen = value;
27+
});
28+
29+
mode.subscribe(value => {
30+
currentMode = value;
31+
});
32+
33+
34+
const languageConf = new Compartment
35+
36+
function createCmdHandler(el){
37+
// Creates a codemirror cmd handler that calls the el.evaluate when an event
38+
// triggers that specific cmd
39+
const toggleCheckbox:StateCommand = ({ state, dispatch }) => {
40+
return el.evaluate(state)
41+
}
42+
return toggleCheckbox
43+
}
44+
45+
46+
export class PyRepl extends HTMLElement {
47+
shadow: ShadowRoot;
48+
wrapper: HTMLElement;
49+
editor: EditorView;
50+
editorNode: HTMLElement;
51+
code: string;
52+
cm: any;
53+
btnConfig: HTMLElement;
54+
btnRun: HTMLElement;
55+
editorOut: HTMLElement; //HTMLTextAreaElement;
56+
theme: string;
57+
// editorState: EditorState;
58+
59+
constructor() {
60+
super();
61+
62+
// attach shadow so we can preserve the element original innerHtml content
63+
this.shadow = this.attachShadow({ mode: 'open'});
64+
65+
this.wrapper = document.createElement('slot');
66+
67+
// add an extra div where we can attach the codemirror editor
68+
this.editorNode = document.createElement('div');
69+
addClasses(this.editorNode, ["editor-box"])
70+
this.shadow.appendChild(this.wrapper);
71+
}
72+
73+
74+
connectedCallback() {
75+
this.code = this.innerHTML;
76+
this.innerHTML = '';
77+
78+
let extensions = [
79+
basicSetup,
80+
languageConf.of(python()),
81+
keymap.of([
82+
...defaultKeymap,
83+
{ key: "Ctrl-Enter", run: createCmdHandler(this) },
84+
{ key: "Shift-Enter", run: createCmdHandler(this) }
85+
]),
86+
87+
// Event listener function that is called every time an user types something on this editor
88+
// EditorView.updateListener.of((v:ViewUpdate) => {
89+
// if (v.docChanged) {
90+
// console.log(v.changes);
91+
92+
// }
93+
// })
94+
];
95+
96+
if (!this.hasAttribute('theme')) {
97+
this.theme = this.getAttribute('theme');
98+
if (this.theme == 'dark'){
99+
extensions.push(oneDarkTheme);
100+
}
101+
}
102+
103+
let startState = EditorState.create({
104+
doc: this.code.trim(),
105+
extensions: extensions
106+
})
107+
108+
this.editor = new EditorView({
109+
state: startState,
110+
parent: this.editorNode
111+
})
112+
113+
let mainDiv = document.createElement('div');
114+
addClasses(mainDiv, ["parentBox", "flex", "flex-col", "border-4", "border-dashed", "border-gray-200", "rounded-lg"])
115+
// add Editor to main PyScript div
116+
117+
// Butons DIV
118+
var eDiv = document.createElement('div');
119+
addClasses(eDiv, "buttons-box relative top-0 right-0 flex flex-row-reverse space-x-reverse space-x-4 font-mono text-white text-sm font-bold leading-6 dev-buttons-group".split(" "))
120+
eDiv.setAttribute("role", "group");
121+
122+
// Play Button
123+
this.btnRun = document.createElement('button');
124+
this.btnRun.innerHTML = '<svg id="" class="svelte-fa svelte-ps5qeg" style="height:1em;vertical-align:-.125em;transform-origin:center;overflow:visible" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>';
125+
let buttonClasses = ["mr-2", "block", "py-2", "px-4", "rounded-full"];
126+
addClasses(this.btnRun, buttonClasses);
127+
addClasses(this.btnRun, ["bg-green-500"])
128+
eDiv.appendChild(this.btnRun);
129+
130+
this.btnRun.onclick = wrap(this);
131+
132+
function wrap(el: any){
133+
async function evaluatePython() {
134+
el.evaluate()
135+
}
136+
return evaluatePython;
137+
}
138+
139+
// Settings button
140+
this.btnConfig = document.createElement('button');
141+
this.btnConfig.innerHTML = '<svg id="" class="svelte-fa svelte-ps5qeg" style="height:1em;vertical-align:-.125em;transform-origin:center;overflow:visible" viewBox="0 0 512 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(256 256)" transform-origin="128 0"><g transform="translate(0,0) scale(1,1)"><path d="M495.9 166.6C499.2 175.2 496.4 184.9 489.6 191.2L446.3 230.6C447.4 238.9 448 247.4 448 256C448 264.6 447.4 273.1 446.3 281.4L489.6 320.8C496.4 327.1 499.2 336.8 495.9 345.4C491.5 357.3 486.2 368.8 480.2 379.7L475.5 387.8C468.9 398.8 461.5 409.2 453.4 419.1C447.4 426.2 437.7 428.7 428.9 425.9L373.2 408.1C359.8 418.4 344.1 427 329.2 433.6L316.7 490.7C314.7 499.7 307.7 506.1 298.5 508.5C284.7 510.8 270.5 512 255.1 512C241.5 512 227.3 510.8 213.5 508.5C204.3 506.1 197.3 499.7 195.3 490.7L182.8 433.6C167 427 152.2 418.4 138.8 408.1L83.14 425.9C74.3 428.7 64.55 426.2 58.63 419.1C50.52 409.2 43.12 398.8 36.52 387.8L31.84 379.7C25.77 368.8 20.49 357.3 16.06 345.4C12.82 336.8 15.55 327.1 22.41 320.8L65.67 281.4C64.57 273.1 64 264.6 64 256C64 247.4 64.57 238.9 65.67 230.6L22.41 191.2C15.55 184.9 12.82 175.3 16.06 166.6C20.49 154.7 25.78 143.2 31.84 132.3L36.51 124.2C43.12 113.2 50.52 102.8 58.63 92.95C64.55 85.8 74.3 83.32 83.14 86.14L138.8 103.9C152.2 93.56 167 84.96 182.8 78.43L195.3 21.33C197.3 12.25 204.3 5.04 213.5 3.51C227.3 1.201 241.5 0 256 0C270.5 0 284.7 1.201 298.5 3.51C307.7 5.04 314.7 12.25 316.7 21.33L329.2 78.43C344.1 84.96 359.8 93.56 373.2 103.9L428.9 86.14C437.7 83.32 447.4 85.8 453.4 92.95C461.5 102.8 468.9 113.2 475.5 124.2L480.2 132.3C486.2 143.2 491.5 154.7 495.9 166.6V166.6zM256 336C300.2 336 336 300.2 336 255.1C336 211.8 300.2 175.1 256 175.1C211.8 175.1 176 211.8 176 255.1C176 300.2 211.8 336 256 336z" fill="currentColor" transform="translate(-256 -256)"></path></g></g></svg>';
142+
this.btnConfig.onclick = function toggleNavBar(evt){
143+
console.log('clicked');
144+
componentDetailsNavOpen.set(!propertiesNavOpen);
145+
146+
currentComponentDetails.set([
147+
{key: "auto-generate", value: true},
148+
{key:"target", value: "default"},
149+
{key: "source", value: "self"},
150+
{key: "output-mode", value: "clear"}
151+
])
152+
}
153+
154+
addClasses(this.btnConfig, buttonClasses);
155+
addClasses(this.btnConfig, ["bg-blue-500"])
156+
eDiv.appendChild(this.btnConfig);
157+
158+
159+
mainDiv.appendChild(eDiv);
160+
mainDiv.appendChild(this.editorNode);
161+
162+
if (!this.id){
163+
console.log("WARNING: <pyrepl> define with an id. <pyrepl> should always have an id. More than one <pyrepl> on a page won't work otherwise!")
164+
}
165+
166+
if (!this.hasAttribute('exec-id')) {
167+
this.setAttribute("exec-id", "1");
168+
}
169+
170+
if (!this.hasAttribute('root')) {
171+
this.setAttribute("root", this.id);
172+
}
173+
174+
if (this.hasAttribute('target')) {
175+
this.editorOut = document.getElementById(this.getAttribute('target'));
176+
177+
// in this case, the default output-mode is append, if hasn't been specified
178+
if (!this.hasAttribute('output-mode')) {
179+
this.setAttribute('output-mode', 'append');
180+
}
181+
}else{
182+
// Editor Output Div
183+
this.editorOut = document.createElement('div');
184+
this.editorOut.classList.add("output");
185+
this.editorOut.hidden = true;
186+
this.editorOut.id = this.id + "-" + this.getAttribute("exec-id");
187+
188+
// add the output div id there's not target
189+
mainDiv.appendChild(this.editorOut);
190+
}
191+
192+
this.appendChild(mainDiv);
193+
194+
console.log('connected');
195+
}
196+
197+
addToOutput(s: string) {
198+
this.editorOut.innerHTML += "<div>"+s+"</div>";
199+
this.editorOut.hidden = false;
200+
}
201+
202+
async evaluate() {
203+
console.log('evaluate');
204+
let pyodide = await pyodideReadyPromise;
205+
// debugger
206+
try {
207+
// @ts-ignore
208+
let source = this.editor.state.doc.toString();
209+
let output;
210+
if (source.includes("asyncio")){
211+
output = pyodide.runPythonAsync(source);
212+
}else{
213+
output = pyodide.runPython(source);
214+
}
215+
216+
if (output !== undefined){
217+
let Element = pyodide.globals.get('Element');
218+
let out = Element(this.editorOut.id);
219+
// @ts-ignore
220+
out.write(output);
221+
out.write.callKwargs(output, { append : false});
222+
223+
if (!this.hasAttribute('target')) {
224+
this.editorOut.hidden = false;
225+
}
226+
// this.addToOutput(output);
227+
}
228+
229+
if (this.hasAttribute('auto-generate')) {
230+
let nextExecId = parseInt(this.getAttribute('exec-id')) + 1;
231+
const newPyRepl = document.createElement("py-repl");
232+
newPyRepl.setAttribute('root', this.getAttribute('root'));
233+
newPyRepl.id = this.getAttribute('root') + "-" + nextExecId.toString();
234+
newPyRepl.setAttribute('auto-generate', null);
235+
if (this.hasAttribute('target')){
236+
newPyRepl.setAttribute('target', this.getAttribute('target'));
237+
}
238+
239+
newPyRepl.setAttribute('exec-id', nextExecId.toString());
240+
this.parentElement.appendChild(newPyRepl);
241+
}
242+
} catch (err) {
243+
this.addToOutput(err);
244+
}
245+
}
246+
247+
render(){
248+
console.log('rendered');
249+
250+
}
251+
}
252+
253+

pyscriptjs/src/main.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import { keymap } from "@codemirror/view";
66
import { defaultKeymap } from "@codemirror/commands";
77
import { oneDarkTheme } from "@codemirror/theme-one-dark";
88
import { PyScript } from "./components/pyscript";
9-
import { pyodideLoaded } from './stores';
10-
9+
import { PyRepl } from "./components/pyrepl";
1110

1211

1312
let xPyScript = customElements.define('py-script', PyScript);
13+
let xPyRepl = customElements.define('py-repl', PyRepl);
1414

1515

1616
const app = new App({

pyscriptjs/src/stores.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const pyodideLoaded = writable({
99
});
1010

1111
export const loadedEnvironments = writable([{}])
12-
12+
export const DEFAULT_MODE = 'play';
1313

1414
export const pyodideReadyPromise = promisable(
1515
loadInterpreter,
@@ -20,4 +20,16 @@ export const navBarOpen = writable(false);
2020
export const componentsNavOpen = writable(false);
2121
export const componentDetailsNavOpen = writable(false);
2222
export const mainDiv = writable(null);
23-
export const currentComponentDetails = writable([]);
23+
export const currentComponentDetails = writable([]);
24+
export const mode = writable(DEFAULT_MODE)
25+
export const scriptsQueue = writable([])
26+
27+
let scriptsQueue_ = []
28+
29+
scriptsQueue.subscribe(value => {
30+
scriptsQueue_ = value;
31+
});
32+
33+
export const addToScriptsQueue = (script) => {
34+
scriptsQueue.set([...scriptsQueue_, script]);
35+
};

0 commit comments

Comments
 (0)