1
0
mirror of https://github.com/pyscript/pyscript.git synced 2022-05-01 19:47:48 +03:00
Files
pyscript-python-html/pyscriptjs/src/components/base.ts
2022-04-19 19:24:22 -05:00

322 lines
9.5 KiB
TypeScript

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 += "<div>"+s+"</div>";
this.outputElement.hidden = false;
}
postEvaluate(){
}
getSourceFromElement(): string{
return "";
}
async getSourceFromFile(s: string): Promise<string>{
let pyodide = await pyodideReadyPromise;
let response = await fetch(s);
this.code = await response.text();
return this.code;
}
protected async _register_esm(pyodide: PyodideInterface): Promise<void> {
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<void> {
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<void> {
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<string>{
let pyodide = await pyodideReadyPromise;
let response = await fetch(s);
return await response.text();
}
async eval(source: string): Promise<void> {
let output;
let pyodide = await pyodideReadyPromise;
try{
output = await pyodide.runPythonAsync(source);
if (output !== undefined){
console.log(output);
}
} catch (err) {
console.log(err);
}
}
}