From 9becc5381fc191d32c38741e90a7e109c296a186 Mon Sep 17 00:00:00 2001 From: Fabio Pliger Date: Mon, 11 Apr 2022 20:11:40 -0500 Subject: [PATCH 1/6] add rollup-plugin-serve plugin to serve from examples folder when in dev --- pyscriptjs/package-lock.json | 22 ++++++++++++++++++++++ pyscriptjs/package.json | 1 + pyscriptjs/rollup.config.js | 9 +++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/pyscriptjs/package-lock.json b/pyscriptjs/package-lock.json index c98ca8d..30e4ade 100644 --- a/pyscriptjs/package-lock.json +++ b/pyscriptjs/package-lock.json @@ -1211,6 +1211,12 @@ "picomatch": "^2.3.1" } }, + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true + }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -1305,6 +1311,12 @@ "wrappy": "1" } }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true + }, "opts": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/opts/-/opts-2.0.2.tgz", @@ -1581,6 +1593,16 @@ "livereload": "^0.9.1" } }, + "rollup-plugin-serve": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-serve/-/rollup-plugin-serve-1.1.0.tgz", + "integrity": "sha512-pYkSsuA0/psKqhhictkJw1c2klya5b+LlCvipWqI9OE1aG2M97mRumZCbBlry5CMEOzYBBgSDgd1694sNbmyIw==", + "dev": true, + "requires": { + "mime": ">=2.4.6", + "opener": "1" + } + }, "rollup-plugin-svelte": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/rollup-plugin-svelte/-/rollup-plugin-svelte-7.1.0.tgz", diff --git a/pyscriptjs/package.json b/pyscriptjs/package.json index 87bea33..ec97fbd 100644 --- a/pyscriptjs/package.json +++ b/pyscriptjs/package.json @@ -17,6 +17,7 @@ "rollup": "^2.3.4", "rollup-plugin-css-only": "^3.1.0", "rollup-plugin-livereload": "^2.0.0", + "rollup-plugin-serve": "^1.1.0", "rollup-plugin-svelte": "^7.0.0", "rollup-plugin-terser": "^7.0.0", "svelte": "^3.0.0", diff --git a/pyscriptjs/rollup.config.js b/pyscriptjs/rollup.config.js index d95a17d..6b6be39 100644 --- a/pyscriptjs/rollup.config.js +++ b/pyscriptjs/rollup.config.js @@ -6,10 +6,11 @@ import { terser } from "rollup-plugin-terser"; import sveltePreprocess from "svelte-preprocess"; import typescript from "@rollup/plugin-typescript"; import css from "rollup-plugin-css-only"; +import serve from 'rollup-plugin-serve' const production = !process.env.ROLLUP_WATCH; -function serve() { +function serve_() { let server; function toExit() { @@ -40,7 +41,7 @@ export default { sourcemap: true, format: "iife", name: "app", - file: "build/pyscript.js", + file: "examples/build/pyscript.js", }, plugins: [ svelte({ @@ -67,6 +68,10 @@ export default { !production && serve(), !production && livereload("public"), production && terser(), + serve({ + port: 8080, + contentBase: 'examples'} + ) ], watch: { clearScreen: false, From 8dc1ade1e21f4d39efbf47e2fac4d9792fe4f9f1 Mon Sep 17 00:00:00 2001 From: Fabio Pliger Date: Mon, 11 Apr 2022 20:13:57 -0500 Subject: [PATCH 2/6] add (TEMP) timer to post initializers until we have proper state management and bump pyodide version --- pyscriptjs/src/App.svelte | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pyscriptjs/src/App.svelte b/pyscriptjs/src/App.svelte index 9d20cee..9d69dea 100644 --- a/pyscriptjs/src/App.svelte +++ b/pyscriptjs/src/App.svelte @@ -49,9 +49,12 @@ } // now we call all post initializers AFTER we actually executed all page scripts - for (let initializer of $postInitializers){ - initializer(); - } + setTimeout(() => { + for (let initializer of $postInitializers){ + initializer(); + } + }, 5000); + } function toggleComponentsNavBar(evt){ @@ -61,7 +64,7 @@ - + From 973a14baeacb5c3b9538c432211e1bfa18e52deb Mon Sep 17 00:00:00 2001 From: Fabio Pliger Date: Mon, 11 Apr 2022 23:13:06 -0500 Subject: [PATCH 3/6] allow pyscript to specify source and try loading pyscript python injections from file --- pyscriptjs/examples/todo.html | 47 ++++----------------------- pyscriptjs/src/components/pyscript.ts | 38 ++++++++++++++++++++-- pyscriptjs/src/interpreter.ts | 16 +++++++-- 3 files changed, 56 insertions(+), 45 deletions(-) diff --git a/pyscriptjs/examples/todo.html b/pyscriptjs/examples/todo.html index 1690441..61a75dc 100644 --- a/pyscriptjs/examples/todo.html +++ b/pyscriptjs/examples/todo.html @@ -7,50 +7,14 @@ Todo App - + - + - -from datetime import datetime as dt - -tasks = [] - -# define the task template that will be use to render new templates to the page -task_template = Element("task-template").select('.task', from_content=True) -task_list = Element("list-tasks-container") -new_task_content = Element("new-task-content") - -def add_task(*ags, **kws): - # create task - task_id = f"task-{len(tasks)}" - task = {"id": task_id, "content": new_task_content.element.value, "done": False, "created_at": dt.now()} - tasks.append(task) - - # add the task element to the page as new node in the list by cloning from a template - taskHtml = task_template.clone(task_id, to=task_list) - taskHtmlContent = taskHtml.select('p') - taskHtmlContent.element.innerText = task['content'] - taskHtmlCheck = taskHtml.select('input') - task_list.element.appendChild(taskHtml.element) - - def check_task(evt=None): - task['done'] = not task['done'] - if task['done']: - taskHtmlContent.element.classList.add("line-through") - else: - taskHtmlContent.element.classList.remove("line-through") - - new_task_content.clear() - taskHtmlCheck.element.onclick = check_task - -def add_task_event(e): - console.log("im in") - if (e.key == "Enter"): - add_task() - - + + +
@@ -80,4 +44,5 @@ def add_task_event(e):
+ diff --git a/pyscriptjs/src/components/pyscript.ts b/pyscriptjs/src/components/pyscript.ts index 572fbdc..80cfcbe 100644 --- a/pyscriptjs/src/components/pyscript.ts +++ b/pyscriptjs/src/components/pyscript.ts @@ -95,6 +95,7 @@ export class PyScript extends HTMLElement { btnConfig: HTMLElement; btnRun: HTMLElement; editorOut: HTMLElement; //HTMLTextAreaElement; + source: string; // editorState: EditorState; constructor() { @@ -205,6 +206,10 @@ export class PyScript extends HTMLElement { } console.log('connected'); + + if (this.hasAttribute('src')) { + this.source = this.getAttribute('src'); + } } addToOutput(s: string) { @@ -212,8 +217,36 @@ export class PyScript extends HTMLElement { 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(); + } + async evaluate() { + console.log('evaluate'); + if (this.source){ + this.loadFromFile(this.source) + }else{ let pyodide = await pyodideReadyPromise; // debugger try { @@ -238,8 +271,9 @@ export class PyScript extends HTMLElement { this.addToOutput(err); console.log(err); } + } } - + render(){ console.log('rendered'); @@ -298,5 +332,5 @@ async function mountElements() { } await pyodide.runPythonAsync(source); } -addPostInitializer(initHandlers); addInitializer(mountElements); +addPostInitializer(initHandlers); diff --git a/pyscriptjs/src/interpreter.ts b/pyscriptjs/src/interpreter.ts index 9db0452..336fd20 100644 --- a/pyscriptjs/src/interpreter.ts +++ b/pyscriptjs/src/interpreter.ts @@ -98,17 +98,29 @@ pyscript = PyScript() let loadInterpreter = async function(): any { /* @ts-ignore */ + console.log("creating pyodide runtime"); pyodide = await loadPyodide({ - indexURL: "https://cdn.jsdelivr.net/pyodide/v0.19.0/full/", stdout: console.log, stderr: console.log }); // now that we loaded, add additional convenience fuctions + console.log("loading micropip"); await pyodide.loadPackage("micropip"); + console.log('loading pyscript module'); + await pyodide.runPythonAsync(` + from pyodide.http import pyfetch + response = await pyfetch("/build/pyscript.py") + with open("pyscript.py", "wb") as f: + content = await response.bytes() + print(content) + f.write(content) + `) + let pkg = pyodide.pyimport("pyscript"); + console.log("creating additional definitions"); let output = pyodide.runPython(additional_definitions); - + console.log("done setting up environment"); /* @ts-ignore */ return pyodide; } From 8f37afb972690e84b93fef49db1a2cb44fabdca7 Mon Sep 17 00:00:00 2001 From: Fabio Pliger Date: Mon, 11 Apr 2022 23:16:48 -0500 Subject: [PATCH 4/6] add python file for todo app --- pyscriptjs/examples/todo.py | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 pyscriptjs/examples/todo.py diff --git a/pyscriptjs/examples/todo.py b/pyscriptjs/examples/todo.py new file mode 100644 index 0000000..1e3588d --- /dev/null +++ b/pyscriptjs/examples/todo.py @@ -0,0 +1,38 @@ +from datetime import datetime as dt +from pyscript import Element +from js import console + +tasks = [] + +# define the task template that will be use to render new templates to the page +task_template = Element("task-template").select('.task', from_content=True) +task_list = Element("list-tasks-container") +new_task_content = Element("new-task-content") + +def add_task(*ags, **kws): + # create task + task_id = f"task-{len(tasks)}" + task = {"id": task_id, "content": new_task_content.element.value, "done": False, "created_at": dt.now()} + tasks.append(task) + + # add the task element to the page as new node in the list by cloning from a template + taskHtml = task_template.clone(task_id, to=task_list) + taskHtmlContent = taskHtml.select('p') + taskHtmlContent.element.innerText = task['content'] + taskHtmlCheck = taskHtml.select('input') + task_list.element.appendChild(taskHtml.element) + + def check_task(evt=None): + task['done'] = not task['done'] + if task['done']: + taskHtmlContent.element.classList.add("line-through") + else: + taskHtmlContent.element.classList.remove("line-through") + + new_task_content.clear() + taskHtmlCheck.element.onclick = check_task + +def add_task_event(e): + console.log("im in") + if (e.key == "Enter"): + add_task() From 63c882f354a77207f3aaa2a5c6dc3d98f25609a5 Mon Sep 17 00:00:00 2001 From: Fabio Pliger Date: Tue, 12 Apr 2022 10:21:02 -0500 Subject: [PATCH 5/6] add pyscript python file with python convenience code --- pyscriptjs/src/pyscript.py | 90 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 pyscriptjs/src/pyscript.py diff --git a/pyscriptjs/src/pyscript.py b/pyscriptjs/src/pyscript.py new file mode 100644 index 0000000..4bd69d2 --- /dev/null +++ b/pyscriptjs/src/pyscript.py @@ -0,0 +1,90 @@ +from js import document, setInterval, console +import asyncio +import io, base64 + +loop = asyncio.get_event_loop() + +class PyScript: + loop = loop + + @staticmethod + def write(element_id, value, append=False, exec_id=0): + """Writes value to the element with id "element_id""" + console.log(f"APPENDING: {append} ==> {element_id} --> {value}") + if append: + child = document.createElement('div'); + element = document.querySelector(f'#{element_id}'); + exec_id = exec_id or element.childElementCount + 1 + element_id = child.id = f"{element_id}-{exec_id}"; + element.appendChild(child); + + if hasattr(value, "savefig"): + console.log(f"FIGURE: {value}") + buf = io.BytesIO() + value.savefig(buf, format='png') + buf.seek(0) + img_str = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8') + document.getElementById(element_id).innerHTML = f'
' + elif hasattr(value, "startswith") and value.startswith("data:image"): + console.log(f"DATA/IMAGE: {value}") + document.getElementById(element_id).innerHTML = f'
' + else: + document.getElementById(element_id).innerHTML = repr(value); + console.log(f"ELSE: {append} ==> {element_id} --> {value}") + + @staticmethod + def run_until_complete(f): + p = loop.run_until_complete(f) + + +class Element: + def __init__(self, element_id, element=None): + self._id = element_id + self._element = element + + @property + def element(self): + """Return the dom element""" + if not self._element: + self._element = document.querySelector(f'#{self._id}'); + return self._element + + def write(self, value, append=False): + console.log(f"Element.write: {value} --> {append}") + # TODO: it should be the opposite... pyscript.write should use the Element.write + # so we can consolidate on how we write depending on the element type + pyscript.write(self._id, value, append=append) + + def clear(self): + if hasattr(self.element, 'value'): + self.element.value = '' + else: + self.write("", append=False) + + def select(self, query, from_content=False): + el = self.element + if from_content: + el = el.content + + _el = el.querySelector(query) + if _el: + return Element(_el.id, _el) + else: + console.log(f"WARNING: can't find element matching query {query}") + + def clone(self, new_id=None, to=None): + if new_id is None: + new_id = self.element.id + + clone = self.element.cloneNode(True); + clone.id = new_id; + + if to: + to.element.appendChild(clone) + + # Inject it into the DOM + self.element.after(clone); + + return Element(clone.id, clone) + +pyscript = PyScript() \ No newline at end of file From 8046e280975f3736dc88301d4f279f4b9e50ba26 Mon Sep 17 00:00:00 2001 From: Fabio Pliger Date: Tue, 12 Apr 2022 10:22:12 -0500 Subject: [PATCH 6/6] fix space --- pyscriptjs/src/pyscript.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyscriptjs/src/pyscript.py b/pyscriptjs/src/pyscript.py index 4bd69d2..81fb12d 100644 --- a/pyscriptjs/src/pyscript.py +++ b/pyscriptjs/src/pyscript.py @@ -87,4 +87,4 @@ class Element: return Element(clone.id, clone) -pyscript = PyScript() \ No newline at end of file +pyscript = PyScript()