mirror of
https://github.com/pyscript/pyscript.git
synced 2022-05-01 19:47:48 +03:00
Merge pull request #28 from anaconda/pys-19/allow_out_err_redirect
[PYS-19] allow out err redirect
This commit is contained in:
@@ -23,7 +23,7 @@
|
|||||||
<h1>Bokeh Example</h1>
|
<h1>Bokeh Example</h1>
|
||||||
<div id="myplot"></div>
|
<div id="myplot"></div>
|
||||||
|
|
||||||
<py-script>
|
<py-script id="main">
|
||||||
import json
|
import json
|
||||||
import pyodide
|
import pyodide
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<h1>Bokeh Example</h1>
|
<h1>Bokeh Example</h1>
|
||||||
<div id="myplot"></div>
|
<div id="myplot"></div>
|
||||||
|
|
||||||
<py-script>
|
<py-script id="main">
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import pyodide
|
import pyodide
|
||||||
|
|||||||
@@ -20,9 +20,12 @@
|
|||||||
</py-env>
|
</py-env>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<h1 class="font-semibold text-2xl ml-5">Custom REPL</h1>
|
||||||
<py-box widths="2/3;1/3">
|
<py-box widths="2/3;1/3">
|
||||||
<py-repl id="my-repl" auto-generate="true" target="output"> </py-repl>
|
<py-repl id="my-repl" auto-generate="true" std-out="output" std-err="err-div"> </py-repl>
|
||||||
<div id="output"></div>
|
<div id="output"></div>
|
||||||
</py-box>
|
</py-box>
|
||||||
|
<footer id="err-div" class="bg-red-700 text-white text-center border-t-4 border-gree-500 fixed inset-x-0 bottom-0 p-4 hidden">
|
||||||
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="outputDiv" class="font-mono" style="background-color:yellow"></div>
|
<div id="outputDiv" class="font-mono" style="background-color:yellow"></div>
|
||||||
<py-script target="outputDiv">
|
<py-script output="outputDiv">
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
now.strftime("%m/%d/%Y, %H:%M:%S")
|
now.strftime("%m/%d/%Y, %H:%M:%S")
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<div class="font-mono">start time: <label id="outputDiv"></label></div>
|
<div class="font-mono">start time: <label id="outputDiv"></label></div>
|
||||||
<div id="outputDiv2" class="font-mono"></div>
|
<div id="outputDiv2" class="font-mono"></div>
|
||||||
<div id="outputDiv3" class="font-mono"></div>
|
<div id="outputDiv3" class="font-mono"></div>
|
||||||
<py-script target="outputDiv">
|
<py-script output="outputDiv">
|
||||||
import utils
|
import utils
|
||||||
utils.now()
|
utils.now()
|
||||||
</py-script>
|
</py-script>
|
||||||
|
|||||||
153
pyscriptjs/src/components/base.ts
Normal file
153
pyscriptjs/src/components/base.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,7 +58,7 @@ export class PyBox extends HTMLElement {
|
|||||||
|
|
||||||
for (let i in this.widths) {
|
for (let i in this.widths) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
addClasses(mainDiv.childNodes[parseInt(i)], [this.widths[i]]);
|
addClasses(mainDiv.childNodes[parseInt(i)], [this.widths[i], 'mx-4']);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.appendChild(mainDiv);
|
this.appendChild(mainDiv);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { oneDarkTheme } from "@codemirror/theme-one-dark";
|
|||||||
|
|
||||||
import { pyodideLoaded, loadedEnvironments, componentDetailsNavOpen, currentComponentDetails, mode } from '../stores';
|
import { pyodideLoaded, loadedEnvironments, componentDetailsNavOpen, currentComponentDetails, mode } from '../stores';
|
||||||
import { addClasses } from '../utils';
|
import { addClasses } from '../utils';
|
||||||
|
import { BaseEvalElement } from './base';
|
||||||
|
|
||||||
// Premise used to connect to the first available pyodide interpreter
|
// Premise used to connect to the first available pyodide interpreter
|
||||||
let pyodideReadyPromise;
|
let pyodideReadyPromise;
|
||||||
@@ -43,27 +44,13 @@ function createCmdHandler(el){
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class PyRepl extends HTMLElement {
|
export class PyRepl extends BaseEvalElement {
|
||||||
shadow: ShadowRoot;
|
|
||||||
wrapper: HTMLElement;
|
|
||||||
editor: EditorView;
|
editor: EditorView;
|
||||||
editorNode: HTMLElement;
|
editorNode: HTMLElement;
|
||||||
code: string;
|
|
||||||
cm: any;
|
|
||||||
btnConfig: HTMLElement;
|
|
||||||
btnRun: HTMLElement;
|
|
||||||
editorOut: HTMLElement; //HTMLTextAreaElement;
|
|
||||||
theme: string;
|
|
||||||
// editorState: EditorState;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
// attach shadow so we can preserve the element original innerHtml content
|
|
||||||
this.shadow = this.attachShadow({ mode: 'open'});
|
|
||||||
|
|
||||||
this.wrapper = document.createElement('slot');
|
|
||||||
|
|
||||||
// add an extra div where we can attach the codemirror editor
|
// add an extra div where we can attach the codemirror editor
|
||||||
this.editorNode = document.createElement('div');
|
this.editorNode = document.createElement('div');
|
||||||
addClasses(this.editorNode, ["editor-box"])
|
addClasses(this.editorNode, ["editor-box"])
|
||||||
@@ -111,7 +98,7 @@ export class PyRepl extends HTMLElement {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let mainDiv = document.createElement('div');
|
let mainDiv = document.createElement('div');
|
||||||
addClasses(mainDiv, ["parentBox", "group", "flex", "flex-col", "mt-10", "border-2", "border-gray-200", "rounded-lg"])
|
addClasses(mainDiv, ["parentBox", "group", "flex", "flex-col", "mt-2", "border-2", "border-gray-200", "rounded-lg"])
|
||||||
// add Editor to main PyScript div
|
// add Editor to main PyScript div
|
||||||
|
|
||||||
// Butons DIV
|
// Butons DIV
|
||||||
@@ -145,7 +132,7 @@ export class PyRepl extends HTMLElement {
|
|||||||
|
|
||||||
currentComponentDetails.set([
|
currentComponentDetails.set([
|
||||||
{key: "auto-generate", value: true},
|
{key: "auto-generate", value: true},
|
||||||
{key:"target", value: "default"},
|
{key:"output", value: "default"},
|
||||||
{key: "source", value: "self"},
|
{key: "source", value: "self"},
|
||||||
{key: "output-mode", value: "clear"}
|
{key: "output-mode", value: "clear"}
|
||||||
])
|
])
|
||||||
@@ -171,83 +158,77 @@ export class PyRepl extends HTMLElement {
|
|||||||
this.setAttribute("root", this.id);
|
this.setAttribute("root", this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasAttribute('target')) {
|
if (this.hasAttribute('output')) {
|
||||||
this.editorOut = document.getElementById(this.getAttribute('target'));
|
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
|
||||||
|
|
||||||
// in this case, the default output-mode is append, if hasn't been specified
|
// in this case, the default output-mode is append, if hasn't been specified
|
||||||
if (!this.hasAttribute('output-mode')) {
|
if (!this.hasAttribute('output-mode')) {
|
||||||
this.setAttribute('output-mode', 'append');
|
this.setAttribute('output-mode', 'append');
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
// Editor Output Div
|
if (this.hasAttribute('std-out')){
|
||||||
this.editorOut = document.createElement('div');
|
this.outputElement = document.getElementById(this.getAttribute('std-out'));
|
||||||
this.editorOut.classList.add("output");
|
}else{
|
||||||
this.editorOut.hidden = true;
|
// In this case neither output or std-out have been provided so we need
|
||||||
this.editorOut.id = this.id + "-" + this.getAttribute("exec-id");
|
// 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 there's not target
|
// add the output div id if there's not output pre-defined
|
||||||
mainDiv.appendChild(this.editorOut);
|
mainDiv.appendChild(this.outputElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasAttribute('std-err')){
|
||||||
|
this.errorElement = document.getElementById(this.getAttribute('std-err'));
|
||||||
|
}else{
|
||||||
|
this.errorElement = this.outputElement;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.appendChild(mainDiv);
|
this.appendChild(mainDiv);
|
||||||
this.editor.focus();
|
this.editor.focus();
|
||||||
console.log('connected');
|
console.log('connected');
|
||||||
}
|
}
|
||||||
|
|
||||||
addToOutput(s: string) {
|
addToOutput(s: string) {
|
||||||
this.editorOut.innerHTML += "<div>"+s+"</div>";
|
this.outputElement.innerHTML += "<div>"+s+"</div>";
|
||||||
this.editorOut.hidden = false;
|
this.outputElement.hidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluate() {
|
postEvaluate(): void {
|
||||||
console.log('evaluate');
|
if (this.hasAttribute('auto-generate')) {
|
||||||
let pyodide = await pyodideReadyPromise;
|
let nextExecId = parseInt(this.getAttribute('exec-id')) + 1;
|
||||||
// debugger
|
const newPyRepl = document.createElement("py-repl");
|
||||||
try {
|
newPyRepl.setAttribute('root', this.getAttribute('root'));
|
||||||
// @ts-ignore
|
newPyRepl.id = this.getAttribute('root') + "-" + nextExecId.toString();
|
||||||
let source = this.editor.state.doc.toString();
|
newPyRepl.setAttribute('auto-generate', null);
|
||||||
let output;
|
if (this.hasAttribute('output')){
|
||||||
if (source.includes("asyncio")){
|
newPyRepl.setAttribute('output', this.getAttribute('output'));
|
||||||
output = await pyodide.runPythonAsync(source);
|
}
|
||||||
}else{
|
if (this.hasAttribute('std-out')){
|
||||||
output = pyodide.runPython(source);
|
newPyRepl.setAttribute('std-out', this.getAttribute('std-out'));
|
||||||
}
|
}
|
||||||
|
if (this.hasAttribute('std-err')){
|
||||||
if (output !== undefined){
|
newPyRepl.setAttribute('std-err', this.getAttribute('std-err'));
|
||||||
let Element = pyodide.globals.get('Element');
|
}
|
||||||
let out = Element(this.editorOut.id);
|
|
||||||
// @ts-ignore
|
newPyRepl.setAttribute('exec-id', nextExecId.toString());
|
||||||
out.write(output);
|
this.parentElement.appendChild(newPyRepl);
|
||||||
out.write.callKwargs(output, { append : false});
|
|
||||||
|
|
||||||
if (!this.hasAttribute('target')) {
|
|
||||||
this.editorOut.hidden = false;
|
|
||||||
}
|
|
||||||
// this.addToOutput(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hasAttribute('auto-generate')) {
|
|
||||||
let nextExecId = parseInt(this.getAttribute('exec-id')) + 1;
|
|
||||||
const newPyRepl = document.createElement("py-repl");
|
|
||||||
newPyRepl.setAttribute('root', this.getAttribute('root'));
|
|
||||||
newPyRepl.id = this.getAttribute('root') + "-" + nextExecId.toString();
|
|
||||||
newPyRepl.setAttribute('auto-generate', null);
|
|
||||||
if (this.hasAttribute('target')){
|
|
||||||
newPyRepl.setAttribute('target', this.getAttribute('target'));
|
|
||||||
}
|
|
||||||
|
|
||||||
newPyRepl.setAttribute('exec-id', nextExecId.toString());
|
|
||||||
this.parentElement.appendChild(newPyRepl);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
this.addToOutput(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSourceFromElement(): string {
|
||||||
|
const sourceStrings = [`output_manager.change("`+this.outputElement.id+`")`,
|
||||||
|
...this.editor.state.doc.toString().split("\n")];
|
||||||
|
return sourceStrings.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
render(){
|
render(){
|
||||||
console.log('rendered');
|
console.log('rendered');
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ import { oneDarkTheme } from "@codemirror/theme-one-dark";
|
|||||||
|
|
||||||
import { pyodideLoaded, loadedEnvironments, componentDetailsNavOpen, currentComponentDetails, mode, addToScriptsQueue, addInitializer, addPostInitializer } from '../stores';
|
import { pyodideLoaded, loadedEnvironments, componentDetailsNavOpen, currentComponentDetails, mode, addToScriptsQueue, addInitializer, addPostInitializer } from '../stores';
|
||||||
import { addClasses } from '../utils';
|
import { addClasses } from '../utils';
|
||||||
|
import { BaseEvalElement } from './base';
|
||||||
|
|
||||||
// Premise used to connect to the first available pyodide interpreter
|
// Premise used to connect to the first available pyodide interpreter
|
||||||
let pyodideReadyPromise;
|
let pyodideReadyPromise;
|
||||||
@@ -50,13 +51,15 @@ type PyodideInterface = {
|
|||||||
registerJsModule(name: string, module: object): void
|
registerJsModule(name: string, module: object): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This should be used as base for generic scripts that need exectutoin
|
||||||
|
// from PyScript to initializers, etc...
|
||||||
class Script {
|
class Script {
|
||||||
source: string;
|
source: string;
|
||||||
state: string;
|
state: string;
|
||||||
target: string;
|
output: string;
|
||||||
|
|
||||||
constructor(source: string, target: string) {
|
constructor(source: string, output: string) {
|
||||||
this.target = target;
|
this.output = output;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.state = 'waiting';
|
this.state = 'waiting';
|
||||||
}
|
}
|
||||||
@@ -75,7 +78,7 @@ class Script {
|
|||||||
output = pyodide.runPython(this.source);
|
output = pyodide.runPython(this.source);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.target){
|
if (this.output){
|
||||||
// this.editorOut.innerHTML = s;
|
// this.editorOut.innerHTML = s;
|
||||||
}
|
}
|
||||||
// if (output !== undefined){
|
// if (output !== undefined){
|
||||||
@@ -90,30 +93,12 @@ class Script {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PyScript extends HTMLElement {
|
export class PyScript extends BaseEvalElement {
|
||||||
shadow: ShadowRoot;
|
|
||||||
wrapper: HTMLElement;
|
|
||||||
editor: EditorView;
|
|
||||||
editorNode: HTMLElement;
|
|
||||||
code: string;
|
|
||||||
cm: any;
|
|
||||||
btnConfig: HTMLElement;
|
|
||||||
btnRun: HTMLElement;
|
|
||||||
editorOut: HTMLElement; //HTMLTextAreaElement;
|
|
||||||
source: string;
|
|
||||||
// editorState: EditorState;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
// attach shadow so we can preserve the element original innerHtml content
|
|
||||||
this.shadow = this.attachShadow({ mode: 'open'});
|
|
||||||
|
|
||||||
this.wrapper = document.createElement('slot');
|
|
||||||
|
|
||||||
// add an extra div where we can attach the codemirror editor
|
// add an extra div where we can attach the codemirror editor
|
||||||
this.editorNode = document.createElement('div');
|
|
||||||
addClasses(this.editorNode, ["editor-box"])
|
|
||||||
this.shadow.appendChild(this.wrapper);
|
this.shadow.appendChild(this.wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,11 +125,6 @@ export class PyScript extends HTMLElement {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
this.editor = new EditorView({
|
|
||||||
state: startState,
|
|
||||||
parent: this.editorNode
|
|
||||||
})
|
|
||||||
|
|
||||||
let mainDiv = document.createElement('div');
|
let mainDiv = document.createElement('div');
|
||||||
addClasses(mainDiv, ["parentBox", "flex", "flex-col", "border-4", "border-dashed", "border-gray-200", "rounded-lg"])
|
addClasses(mainDiv, ["parentBox", "flex", "flex-col", "border-4", "border-dashed", "border-gray-200", "rounded-lg"])
|
||||||
// add Editor to main PyScript div
|
// add Editor to main PyScript div
|
||||||
@@ -180,7 +160,7 @@ export class PyScript extends HTMLElement {
|
|||||||
|
|
||||||
currentComponentDetails.set([
|
currentComponentDetails.set([
|
||||||
{key: "auto-generate", value: true},
|
{key: "auto-generate", value: true},
|
||||||
{key:"target", value: "default"},
|
{key:"output", value: "default"},
|
||||||
{key: "source", value: "self"}
|
{key: "source", value: "self"}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@@ -190,18 +170,34 @@ export class PyScript extends HTMLElement {
|
|||||||
eDiv.appendChild(this.btnConfig);
|
eDiv.appendChild(this.btnConfig);
|
||||||
|
|
||||||
mainDiv.appendChild(eDiv);
|
mainDiv.appendChild(eDiv);
|
||||||
mainDiv.appendChild(this.editorNode);
|
|
||||||
|
|
||||||
if (this.hasAttribute('target')) {
|
if (this.hasAttribute('output')) {
|
||||||
this.editorOut = document.getElementById(this.getAttribute('target'));
|
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{
|
}else{
|
||||||
// Editor Output Div
|
if (this.hasAttribute('std-out')){
|
||||||
this.editorOut = document.createElement('div');
|
this.outputElement = document.getElementById(this.getAttribute('std-out'));
|
||||||
this.editorOut.classList.add("output");
|
}else{
|
||||||
this.editorOut.hidden = true;
|
// 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 there's not target
|
// add the output div id if there's not output pre-defined
|
||||||
mainDiv.appendChild(this.editorOut);
|
mainDiv.appendChild(this.outputElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasAttribute('std-err')){
|
||||||
|
this.outputElement = document.getElementById(this.getAttribute('std-err'));
|
||||||
|
}else{
|
||||||
|
this.errorElement = this.outputElement;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentMode=="edit"){
|
if (currentMode=="edit"){
|
||||||
@@ -217,35 +213,6 @@ export class PyScript extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addToOutput(s: string) {
|
|
||||||
this.editorOut.innerHTML = s;
|
|
||||||
this.editorOut.hidden = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadFromFile(s: string){
|
|
||||||
let pyodide = await pyodideReadyPromise;
|
|
||||||
let response = await fetch(s);
|
|
||||||
this.code = await response.text();
|
|
||||||
|
|
||||||
await pyodide.runPythonAsync(this.code);
|
|
||||||
await pyodide.runPythonAsync(`
|
|
||||||
from pyodide.http import pyfetch
|
|
||||||
from pyodide import eval_code
|
|
||||||
response = await pyfetch("`+s+`")
|
|
||||||
content = await response.bytes()
|
|
||||||
|
|
||||||
with open("todo.py", "wb") as f:
|
|
||||||
print(content)
|
|
||||||
f.write(content)
|
|
||||||
print("done writing")
|
|
||||||
`)
|
|
||||||
// let pkg = pyodide.pyimport("todo");
|
|
||||||
// pyodide.runPython(`
|
|
||||||
// import todo
|
|
||||||
// `)
|
|
||||||
// pkg.do_something();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async _register_esm(pyodide: PyodideInterface): Promise<void> {
|
protected async _register_esm(pyodide: PyodideInterface): Promise<void> {
|
||||||
for (const node of document.querySelectorAll("script[type='importmap']")) {
|
for (const node of document.querySelectorAll("script[type='importmap']")) {
|
||||||
const importmap = (() => {
|
const importmap = (() => {
|
||||||
@@ -278,64 +245,8 @@ export class PyScript extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluate(): Promise<void> {
|
getSourceFromElement(): string {
|
||||||
console.log('evaluate');
|
return htmlDecode(this.code);
|
||||||
|
|
||||||
if (this.source){
|
|
||||||
this.loadFromFile(this.source)
|
|
||||||
}else{
|
|
||||||
const pyodide = await pyodideReadyPromise;
|
|
||||||
await this._register_esm(pyodide)
|
|
||||||
// debugger
|
|
||||||
try {
|
|
||||||
function ltrim(code: string): string {
|
|
||||||
const lines = code.split("\n")
|
|
||||||
if (lines.length == 0)
|
|
||||||
return code
|
|
||||||
|
|
||||||
const lengths = lines
|
|
||||||
.filter((line) => line.trim().length != 0)
|
|
||||||
.map((line) => {
|
|
||||||
const [prefix] = line.match(/^\s*/)
|
|
||||||
return prefix.length
|
|
||||||
})
|
|
||||||
|
|
||||||
const k = Math.min(...lengths)
|
|
||||||
|
|
||||||
if (k != 0)
|
|
||||||
return lines.map((line) => line.substring(k)).join("\n")
|
|
||||||
else
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
const str = this.editor.state.doc.toString()
|
|
||||||
const source = htmlDecode(ltrim(str))
|
|
||||||
|
|
||||||
let output
|
|
||||||
if (source.includes("asyncio"))
|
|
||||||
output = await pyodide.runPythonAsync(source)
|
|
||||||
else
|
|
||||||
output = pyodide.runPython(source)
|
|
||||||
|
|
||||||
if (output !== undefined) {
|
|
||||||
this.addToOutput(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hasAttribute('auto-generate') && this.parentElement.lastChild === this) {
|
|
||||||
const newPyscript = document.createElement("py-script");
|
|
||||||
newPyscript.setAttribute('auto-generate', null);
|
|
||||||
this.parentElement.appendChild(newPyscript);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
this.addToOutput(err);
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render(){
|
|
||||||
console.log('rendered');
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ let pyodide;
|
|||||||
let additional_definitions = `
|
let additional_definitions = `
|
||||||
from js import document, setInterval, console
|
from js import document, setInterval, console
|
||||||
import asyncio
|
import asyncio
|
||||||
import io, base64
|
import io, base64, sys
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
@@ -22,6 +22,8 @@ class PyScript:
|
|||||||
if append:
|
if append:
|
||||||
child = document.createElement('div');
|
child = document.createElement('div');
|
||||||
element = document.querySelector(f'#{element_id}');
|
element = document.querySelector(f'#{element_id}');
|
||||||
|
if not element:
|
||||||
|
return
|
||||||
exec_id = exec_id or element.childElementCount + 1
|
exec_id = exec_id or element.childElementCount + 1
|
||||||
element_id = child.id = f"{element_id}-{exec_id}";
|
element_id = child.id = f"{element_id}-{exec_id}";
|
||||||
element.appendChild(child);
|
element.appendChild(child);
|
||||||
@@ -34,11 +36,9 @@ class PyScript:
|
|||||||
img_str = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8')
|
img_str = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8')
|
||||||
document.getElementById(element_id).innerHTML = f'<div><img id="plt" src="{img_str}"/></div>'
|
document.getElementById(element_id).innerHTML = f'<div><img id="plt" src="{img_str}"/></div>'
|
||||||
elif hasattr(value, "startswith") and value.startswith("data:image"):
|
elif hasattr(value, "startswith") and value.startswith("data:image"):
|
||||||
console.log(f"DATA/IMAGE: {value}")
|
|
||||||
document.getElementById(element_id).innerHTML = f'<div><img id="plt" src="{value}"/></div>'
|
document.getElementById(element_id).innerHTML = f'<div><img id="plt" src="{value}"/></div>'
|
||||||
else:
|
else:
|
||||||
document.getElementById(element_id).innerHTML = value;
|
document.getElementById(element_id).innerHTML = value;
|
||||||
console.log(f"ELSE: {append} ==> {element_id} --> {value}")
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run_until_complete(f):
|
def run_until_complete(f):
|
||||||
@@ -95,7 +95,57 @@ class Element:
|
|||||||
|
|
||||||
return Element(clone.id, clone)
|
return Element(clone.id, clone)
|
||||||
|
|
||||||
|
class OutputCtxManager:
|
||||||
|
def __init__(self, out=None, output_to_console=True, append=True):
|
||||||
|
self._out = out
|
||||||
|
self._prev = out
|
||||||
|
self.output_to_console = output_to_console
|
||||||
|
self._append = append
|
||||||
|
|
||||||
|
def change(self, out=None, err=None, output_to_console=True, append=True):
|
||||||
|
self._prevt = self._out
|
||||||
|
self._out = out
|
||||||
|
self.output_to_console = output_to_console
|
||||||
|
self._append = append
|
||||||
|
console.log("----> changed out to", self._out, self._append)
|
||||||
|
|
||||||
|
def revert(self):
|
||||||
|
console.log("----> reverted")
|
||||||
|
self._out = self._prev
|
||||||
|
|
||||||
|
def write(self, txt):
|
||||||
|
console.log('writing to', self._out, txt, self._append)
|
||||||
|
if self._out:
|
||||||
|
pyscript.write(self._out, txt, append=self._append)
|
||||||
|
if self.output_to_console:
|
||||||
|
console.log(self._out, txt)
|
||||||
|
|
||||||
|
class OutputManager:
|
||||||
|
def __init__(self, out=None, err=None, output_to_console=True, append=True):
|
||||||
|
sys.stdout = self._out_manager = OutputCtxManager(out, output_to_console, append)
|
||||||
|
sys.strerr = self._err_manager = OutputCtxManager(err, output_to_console, append)
|
||||||
|
self.output_to_console = output_to_console
|
||||||
|
self._append = append
|
||||||
|
|
||||||
|
def change(self, out=None, err=None, output_to_console=True, append=True):
|
||||||
|
self._out_manager.change(out, output_to_console, append)
|
||||||
|
sys.stdout = self._out_manager
|
||||||
|
self._err_manager.change(err, output_to_console, append)
|
||||||
|
sys.stderr = self._err_manager
|
||||||
|
self.output_to_console = output_to_console
|
||||||
|
self.append = append
|
||||||
|
|
||||||
|
def revert(self):
|
||||||
|
self._out_manager.revert()
|
||||||
|
self._err_manager.revert()
|
||||||
|
sys.stdout = self._out_manager
|
||||||
|
sys.stdout = self._err_manager
|
||||||
|
console.log("----> reverted")
|
||||||
|
|
||||||
|
|
||||||
pyscript = PyScript()
|
pyscript = PyScript()
|
||||||
|
output_manager = OutputManager()
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
let loadInterpreter = async function(): Promise<any> {
|
let loadInterpreter = async function(): Promise<any> {
|
||||||
|
|||||||
@@ -2,5 +2,28 @@
|
|||||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
|
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules/*", "__sapper__/*", "public/*"]
|
"exclude": ["node_modules/*", "__sapper__/*", "public/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"target": "es2017",
|
||||||
|
"module": "esnext",
|
||||||
|
/**
|
||||||
|
Svelte Preprocess cannot figure out whether you have a value or a type, so tell TypeScript
|
||||||
|
to enforce using `import type` instead of `import` for Types.
|
||||||
|
*/
|
||||||
|
"importsNotUsedAsValues": "error",
|
||||||
|
"isolatedModules": true,
|
||||||
|
/**
|
||||||
|
To have warnings/errors of the Svelte compiler at the correct position,
|
||||||
|
enable source maps by default.
|
||||||
|
*/
|
||||||
|
"sourceMap": true,
|
||||||
|
/** Requests the runtime types from the svelte modules by default. Needed for TS files or else you get errors. */
|
||||||
|
"types": ["svelte"],
|
||||||
|
|
||||||
|
"strict": false,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user