import { pyodideLoaded, loadedEnvironments, componentDetailsNavOpen, mode } from '../stores'; // Premise used to connect to the first available pyodide interpreter let pyodideReadyPromise; let environments; let currentMode; let Element; pyodideLoaded.subscribe(value => { pyodideReadyPromise = value; }); loadedEnvironments.subscribe(value => { environments = value; }); let propertiesNavOpen; componentDetailsNavOpen.subscribe(value => { propertiesNavOpen = value; }); mode.subscribe(value => { currentMode = value; }); // TODO: use type declaractions type PyodideInterface = { registerJsModule(name: string, module: object): void } export class BaseEvalElement extends HTMLElement { shadow: ShadowRoot; wrapper: HTMLElement; code: string; source: string; btnConfig: HTMLElement; btnRun: HTMLElement; outputElement: HTMLElement; errorElement: HTMLElement; theme: string; constructor() { super(); // attach shadow so we can preserve the element original innerHtml content this.shadow = this.attachShadow({ mode: 'open'}); this.wrapper = document.createElement('slot'); this.shadow.appendChild(this.wrapper); } addToOutput(s: string) { this.outputElement.innerHTML += "
"+s+"
"; this.outputElement.hidden = false; } postEvaluate(){ } getSourceFromElement(): string{ return ""; } async getSourceFromFile(s: string): Promise{ let pyodide = await pyodideReadyPromise; let response = await fetch(s); this.code = await response.text(); return this.code; } protected async _register_esm(pyodide: PyodideInterface): Promise { const imports: {[key: string]: unknown} = {} for (const node of document.querySelectorAll("script[type='importmap']")) { const importmap = (() => { try { return JSON.parse(node.textContent) } catch { return null } })() if (importmap?.imports == null) continue for (const [name, url] of Object.entries(importmap.imports)) { if (typeof name != "string" || typeof url != "string") continue try { // XXX: pyodide doesn't like Module(), failing with // "can't read 'name' of undefined" at import time imports[name] = {...await import(url)} } catch { console.error(`failed to fetch '${url}' for '${name}'`) } } } pyodide.registerJsModule("esm", imports) } async evaluate(): Promise { console.log('evaluate'); let pyodide = await pyodideReadyPromise; let source: string; let output; try { // @ts-ignore if (this.source){ source = await this.getSourceFromFile(this.source); }else{ source = this.getSourceFromElement(); } await this._register_esm(pyodide); if (source.includes("asyncio")){ await pyodide.runPythonAsync(`output_manager.change("`+this.outputElement.id+`", "`+this.errorElement.id+`")`); output = await pyodide.runPythonAsync(source); await pyodide.runPythonAsync(`output_manager.revert()`) }else{ output = pyodide.runPython(`output_manager.change("`+this.outputElement.id+`", "`+this.errorElement.id+`")`); output = pyodide.runPython(source); pyodide.runPython(`output_manager.revert()`) } if (output !== undefined){ if (Element === undefined){ Element = pyodide.globals.get('Element'); } const out = Element(this.outputElement.id); // @ts-ignore out.write.callKwargs(output, { append : true}); this.outputElement.hidden = false; this.outputElement.style.display = 'block'; } this.postEvaluate() } catch (err) { if (Element === undefined){ Element = pyodide.globals.get('Element'); } const out = Element(this.errorElement.id); // @ts-ignore out.write.callKwargs(err, { append : true}); this.errorElement.hidden = false; this.errorElement.style.display = 'block'; } } } function createWidget(name: string, code: string, klass: string){ class CustomWidget extends HTMLElement{ shadow: ShadowRoot; wrapper: HTMLElement; name: string = name; klass: string = klass; code: string = code; proxy: any; proxyClass: any; constructor() { super(); // attach shadow so we can preserve the element original innerHtml content this.shadow = this.attachShadow({ mode: 'open'}); this.wrapper = document.createElement('slot'); this.shadow.appendChild(this.wrapper); } connectedCallback() { console.log(this.name, 'connected!!!!') this.eval(this.code).then(() => { this.proxy = this.proxyClass(this); console.log('proxy', this.proxy); this.proxy.connect(); this.registerWidget(); }); } async registerWidget(){ let pyodide = await pyodideReadyPromise; console.log('new widget registered:', this.name); pyodide.globals.set(this.id, this.proxy); } async eval(source: string): Promise { let output; let pyodide = await pyodideReadyPromise; try{ output = await pyodide.runPythonAsync(source); this.proxyClass = pyodide.globals.get(this.klass); if (output !== undefined){ console.log(output); } } catch (err) { console.log(err); } } } let xPyWidget = customElements.define(name, CustomWidget); } export class PyWidget extends HTMLElement { shadow: ShadowRoot; name: string; klass: string; outputElement: HTMLElement; errorElement: HTMLElement; wrapper: HTMLElement; theme: string; source: string; code: string; constructor() { super(); // attach shadow so we can preserve the element original innerHtml content this.shadow = this.attachShadow({ mode: 'open'}); this.wrapper = document.createElement('slot'); this.shadow.appendChild(this.wrapper); if (this.hasAttribute('src')) { this.source = this.getAttribute('src'); } if (this.hasAttribute('name')) { this.name = this.getAttribute('name'); } if (this.hasAttribute('klass')) { this.klass = this.getAttribute('klass'); } } connectedCallback() { if (this.id === undefined){ throw new ReferenceError(`No id specified for component. Components must have an explicit id. Please use id="" to specify your component id.`) return; } let mainDiv = document.createElement('div'); mainDiv.id = this.id + '-main'; this.appendChild(mainDiv); console.log('reading source') this.getSourceFromFile(this.source).then((code:string) => { this.code = code; createWidget(this.name, code, this.klass); }); console.log('py-template connected'); } initOutErr(): void { if (this.hasAttribute('output')) { this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output')); // in this case, the default output-mode is append, if hasn't been specified if (!this.hasAttribute('output-mode')) { this.setAttribute('output-mode', 'append'); } }else{ if (this.hasAttribute('std-out')){ this.outputElement = document.getElementById(this.getAttribute('std-out')); }else{ // In this case neither output or std-out have been provided so we need // to create a new output div to output to this.outputElement = document.createElement('div'); this.outputElement.classList.add("output"); this.outputElement.hidden = true; this.outputElement.id = this.id + "-" + this.getAttribute("exec-id"); // add the output div id if there's not output pre-defined //mainDiv.appendChild(this.outputElement); } if (this.hasAttribute('std-err')){ this.outputElement = document.getElementById(this.getAttribute('std-err')); }else{ this.errorElement = this.outputElement; } } } async getSourceFromFile(s: string): Promise{ let pyodide = await pyodideReadyPromise; let response = await fetch(s); return await response.text(); } async eval(source: string): Promise { let output; let pyodide = await pyodideReadyPromise; try{ output = await pyodide.runPythonAsync(source); if (output !== undefined){ console.log(output); } } catch (err) { console.log(err); } } }