fixed inefficient file loading
This commit is contained in:
parent
ea43b2f086
commit
01e083addb
13
build.sh
Executable file
13
build.sh
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
rm -rf ./dist/
|
||||||
|
python setup.py bdist_wheel
|
||||||
|
|
||||||
|
wheel_files=( dist/*whl )
|
||||||
|
|
||||||
|
|
||||||
|
cd ./examples/
|
||||||
|
|
||||||
|
for example in *;
|
||||||
|
do pyscript_bootstrap_app update $example $example --pyscript_bootstrap_templates_wheel_url ../${wheel_files[0]}
|
||||||
|
done
|
Binary file not shown.
12
examples/01_hello_world/config.json
Normal file
12
examples/01_hello_world/config.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"root_folder": "01_hello_world",
|
||||||
|
"title": "01_hello_world",
|
||||||
|
"packages": [],
|
||||||
|
"paths": [],
|
||||||
|
"pyscript_css_url": "https://pyscript.net/alpha/pyscript.css",
|
||||||
|
"pyscript_js_url": "https://pyscript.net/alpha/pyscript.min.js",
|
||||||
|
"pyscript_py_url": "https://pyscript.net/alpha/pyscript.py",
|
||||||
|
"bootstrap_css_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css",
|
||||||
|
"bootstrap_js_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js",
|
||||||
|
"pyscript_bootstrap_templates_wheel_url": "../dist/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl"
|
||||||
|
}
|
29
examples/01_hello_world/index.html
Normal file
29
examples/01_hello_world/index.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" style="width: 100%;height: 100%">
|
||||||
|
<head id="head">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="./resources/pyscript.css" />
|
||||||
|
<script defer src="./resources/pyscript.js"></script>
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="./resources/bootstrap.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<title>01_hello_world</title>
|
||||||
|
|
||||||
|
<py-env>
|
||||||
|
- ./resources/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl
|
||||||
|
- paths:
|
||||||
|
|
||||||
|
</py-env>
|
||||||
|
</head>
|
||||||
|
<body style="width: 100%; height: 100%">
|
||||||
|
<div id="pyscript_app" style="height: 100%; min-height: 100%"></div>
|
||||||
|
<py-script src="./main.py"></py-script>
|
||||||
|
|
||||||
|
<script src="./resources/bootstrap.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
15
examples/01_hello_world/main.py
Normal file
15
examples/01_hello_world/main.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
from pyscript_bootstrap_templates import bootstrap_templates
|
||||||
|
from pyscript_bootstrap_templates import bootstrap_HTML as bHTML
|
||||||
|
from pyscript_bootstrap_templates import HTML as HTML
|
||||||
|
|
||||||
|
app = bootstrap_templates.PyScriptBootstrapDashboard(
|
||||||
|
parent_element="pyscript_app", brand_name="hello_world")
|
||||||
|
div = bHTML.BootstrapContainer("This is a sidebar", parent=app.sidebar)
|
||||||
|
div.font_size = 4
|
||||||
|
|
||||||
|
btn = bHTML.ButtonPrimary("Click me", parent=app.sidebar)
|
||||||
|
btn.w = 100
|
||||||
|
btn.onclick = lambda _: bHTML.AlertSuccess(
|
||||||
|
"You clicked me!", parent=app.main_area)
|
||||||
|
|
Binary file not shown.
15
examples/02_image_filter/config.json
Normal file
15
examples/02_image_filter/config.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"root_folder": "02_image_filter",
|
||||||
|
"title": "02_image_filter",
|
||||||
|
"packages": [
|
||||||
|
"numpy",
|
||||||
|
"scikit-image"
|
||||||
|
],
|
||||||
|
"paths": null,
|
||||||
|
"pyscript_css_url": "https://pyscript.net/alpha/pyscript.css",
|
||||||
|
"pyscript_js_url": "https://pyscript.net/alpha/pyscript.min.js",
|
||||||
|
"pyscript_py_url": "https://pyscript.net/alpha/pyscript.py",
|
||||||
|
"bootstrap_css_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css",
|
||||||
|
"bootstrap_js_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js",
|
||||||
|
"pyscript_bootstrap_templates_wheel_url": "../dist/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl"
|
||||||
|
}
|
@ -11,12 +11,12 @@
|
|||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link href="./resources/bootstrap.css" rel="stylesheet">
|
<link href="./resources/bootstrap.css" rel="stylesheet">
|
||||||
|
|
||||||
<title>sobel_filter</title>
|
<title>02_image_filter</title>
|
||||||
|
|
||||||
<py-env>
|
<py-env>
|
||||||
|
- ./resources/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl
|
||||||
- numpy
|
- numpy
|
||||||
- scikit-image
|
- scikit-image
|
||||||
- ./resources/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl
|
|
||||||
|
|
||||||
</py-env>
|
</py-env>
|
||||||
</head>
|
</head>
|
@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
from pyscript_bootstrap_templates import bootstrap_templates
|
from pyscript_bootstrap_templates import bootstrap_templates
|
||||||
from pyscript_bootstrap_templates import bootstrap_HTML as bHTML
|
from pyscript_bootstrap_templates import bootstrap_HTML as bHTML
|
||||||
from pyscript_bootstrap_templates import bootstrap_inputs as bInputs
|
from pyscript_bootstrap_templates import bootstrap_inputs as bInputs
|
||||||
@ -20,15 +18,10 @@ def sobel_image(img):
|
|||||||
return ((255 * img_sobel) / img_sobel.max()).astype(np.uint8)
|
return ((255 * img_sobel) / img_sobel.max()).astype(np.uint8)
|
||||||
|
|
||||||
|
|
||||||
app = bootstrap_templates.PyScriptBootstrapDashboard(parent_element="pyscript_app", brand_name="Sobel Filter Example")
|
app = bootstrap_templates.PyScriptBootstrapDashboard(parent_element="pyscript_app", brand_name="Image Filter Example")
|
||||||
|
|
||||||
HTML.H3("upload an image:", parent=app.sidebar)
|
|
||||||
HTML.H3("filtered image:", parent=app.main_area)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
image_input = bInputs.InputFile(label_text="choose image file", parent=app.sidebar)
|
image_input = bInputs.InputFile(label_text="choose image file", parent=app.sidebar)
|
||||||
image_input._input.set_attribute("accept", "image/*") # TODO: write wrapper for this
|
image_input.set_attribute("accept", "image/*")
|
||||||
|
|
||||||
def on_image_change(f, *args):
|
def on_image_change(f, *args):
|
||||||
print("image changed")
|
print("image changed")
|
||||||
@ -42,5 +35,4 @@ def on_image_change(f, *args):
|
|||||||
output.rounded_size = 10
|
output.rounded_size = 10
|
||||||
output.shadow = bHTML.Shadow.LARGE
|
output.shadow = bHTML.Shadow.LARGE
|
||||||
|
|
||||||
image_input.onchange = on_image_change
|
image_input.onchange = on_image_change
|
||||||
|
|
7
examples/02_image_filter/resources/bootstrap.css
vendored
Normal file
7
examples/02_image_filter/resources/bootstrap.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
examples/02_image_filter/resources/bootstrap.js
vendored
Normal file
7
examples/02_image_filter/resources/bootstrap.js
vendored
Normal file
File diff suppressed because one or more lines are too long
14
examples/02_image_filter/resources/pyscript.css
Normal file
14
examples/02_image_filter/resources/pyscript.css
Normal file
File diff suppressed because one or more lines are too long
4
examples/02_image_filter/resources/pyscript.js
Normal file
4
examples/02_image_filter/resources/pyscript.js
Normal file
File diff suppressed because one or more lines are too long
424
examples/02_image_filter/resources/pyscript.py
Normal file
424
examples/02_image_filter/resources/pyscript.py
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
import asyncio
|
||||||
|
import base64
|
||||||
|
import io
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from textwrap import dedent
|
||||||
|
|
||||||
|
import micropip # noqa: F401
|
||||||
|
from js import console, document
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
MIME_METHODS = {
|
||||||
|
"__repr__": "text/plain",
|
||||||
|
"_repr_html_": "text/html",
|
||||||
|
"_repr_markdown_": "text/markdown",
|
||||||
|
"_repr_svg_": "image/svg+xml",
|
||||||
|
"_repr_png_": "image/png",
|
||||||
|
"_repr_pdf_": "application/pdf",
|
||||||
|
"_repr_jpeg_": "image/jpeg",
|
||||||
|
"_repr_latex": "text/latex",
|
||||||
|
"_repr_json_": "application/json",
|
||||||
|
"_repr_javascript_": "application/javascript",
|
||||||
|
"savefig": "image/png",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def render_image(mime, value, meta):
|
||||||
|
data = f"data:{mime};charset=utf-8;base64,{value}"
|
||||||
|
attrs = " ".join(['{k}="{v}"' for k, v in meta.items()])
|
||||||
|
return f'<img src="{data}" {attrs}</img>'
|
||||||
|
|
||||||
|
|
||||||
|
def identity(value, meta):
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
MIME_RENDERERS = {
|
||||||
|
"text/plain": identity,
|
||||||
|
"text/html": identity,
|
||||||
|
"image/png": lambda value, meta: render_image("image/png", value, meta),
|
||||||
|
"image/jpeg": lambda value, meta: render_image("image/jpeg", value, meta),
|
||||||
|
"image/svg+xml": identity,
|
||||||
|
"application/json": identity,
|
||||||
|
"application/javascript": lambda value, meta: f"<script>{value}</script>",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def eval_formatter(obj, print_method):
|
||||||
|
"""
|
||||||
|
Evaluates a formatter method.
|
||||||
|
"""
|
||||||
|
if print_method == "__repr__":
|
||||||
|
return repr(obj)
|
||||||
|
elif hasattr(obj, print_method):
|
||||||
|
if print_method == "savefig":
|
||||||
|
buf = io.BytesIO()
|
||||||
|
obj.savefig(buf, format="png")
|
||||||
|
buf.seek(0)
|
||||||
|
return base64.b64encode(buf.read()).decode("utf-8")
|
||||||
|
return getattr(obj, print_method)()
|
||||||
|
elif print_method == "_repr_mimebundle_":
|
||||||
|
return {}, {}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def format_mime(obj):
|
||||||
|
"""
|
||||||
|
Formats object using _repr_x_ methods.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, str):
|
||||||
|
return obj, "text/plain"
|
||||||
|
|
||||||
|
mimebundle = eval_formatter(obj, "_repr_mimebundle_")
|
||||||
|
if isinstance(mimebundle, tuple):
|
||||||
|
format_dict, _ = mimebundle
|
||||||
|
else:
|
||||||
|
format_dict = mimebundle
|
||||||
|
|
||||||
|
output, not_available = None, []
|
||||||
|
for method, mime_type in reversed(MIME_METHODS.items()):
|
||||||
|
if mime_type in format_dict:
|
||||||
|
output = format_dict[mime_type]
|
||||||
|
else:
|
||||||
|
output = eval_formatter(obj, method)
|
||||||
|
|
||||||
|
if output is None:
|
||||||
|
continue
|
||||||
|
elif mime_type not in MIME_RENDERERS:
|
||||||
|
not_available.append(mime_type)
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
if output is None:
|
||||||
|
if not_available:
|
||||||
|
console.warning(
|
||||||
|
f"Rendered object requested unavailable MIME renderers: {not_available}"
|
||||||
|
)
|
||||||
|
output = repr(output)
|
||||||
|
mime_type = "text/plain"
|
||||||
|
elif isinstance(output, tuple):
|
||||||
|
output, meta = output
|
||||||
|
else:
|
||||||
|
meta = {}
|
||||||
|
return MIME_RENDERERS[mime_type](output, meta), mime_type
|
||||||
|
|
||||||
|
|
||||||
|
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}")
|
||||||
|
if not element:
|
||||||
|
return
|
||||||
|
exec_id = exec_id or element.childElementCount + 1
|
||||||
|
element_id = child.id = f"{element_id}-{exec_id}"
|
||||||
|
element.appendChild(child)
|
||||||
|
|
||||||
|
element = document.getElementById(element_id)
|
||||||
|
html, mime_type = format_mime(value)
|
||||||
|
if mime_type in ("application/javascript", "text/html"):
|
||||||
|
script_element = document.createRange().createContextualFragment(html)
|
||||||
|
element.appendChild(script_element)
|
||||||
|
else:
|
||||||
|
element.innerHTML = html
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run_until_complete(f):
|
||||||
|
_ = loop.run_until_complete(f)
|
||||||
|
|
||||||
|
|
||||||
|
class Element:
|
||||||
|
def __init__(self, element_id, element=None):
|
||||||
|
self._id = element_id
|
||||||
|
self._element = element
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self):
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def element(self):
|
||||||
|
"""Return the dom element"""
|
||||||
|
if not self._element:
|
||||||
|
self._element = document.querySelector(f"#{self._id}")
|
||||||
|
return self._element
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self.element.value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def innerHtml(self):
|
||||||
|
return self.element.innerHtml
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def remove_class(self, classname):
|
||||||
|
if isinstance(classname, list):
|
||||||
|
for cl in classname:
|
||||||
|
self.remove_class(cl)
|
||||||
|
else:
|
||||||
|
self.element.classList.remove(classname)
|
||||||
|
|
||||||
|
def add_class(self, classname):
|
||||||
|
self.element.classList.add(classname)
|
||||||
|
|
||||||
|
|
||||||
|
def add_classes(element, class_list):
|
||||||
|
for klass in class_list.split(" "):
|
||||||
|
element.classList.add(klass)
|
||||||
|
|
||||||
|
|
||||||
|
def create(what, id_=None, classes=""):
|
||||||
|
element = document.createElement(what)
|
||||||
|
if id_:
|
||||||
|
element.id = id_
|
||||||
|
add_classes(element, classes)
|
||||||
|
return Element(id_, element)
|
||||||
|
|
||||||
|
|
||||||
|
class PyWidgetTheme:
|
||||||
|
def __init__(self, main_style_classes):
|
||||||
|
self.main_style_classes = main_style_classes
|
||||||
|
|
||||||
|
def theme_it(self, widget):
|
||||||
|
for klass in self.main_style_classes.split(" "):
|
||||||
|
widget.classList.add(klass)
|
||||||
|
|
||||||
|
|
||||||
|
class PyItemTemplate(Element):
|
||||||
|
label_fields = None
|
||||||
|
|
||||||
|
def __init__(self, data, labels=None, state_key=None, parent=None):
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
self.register_parent(parent)
|
||||||
|
|
||||||
|
if not labels:
|
||||||
|
labels = list(self.data.keys())
|
||||||
|
self.labels = labels
|
||||||
|
|
||||||
|
self.state_key = state_key
|
||||||
|
|
||||||
|
super().__init__(self._id)
|
||||||
|
|
||||||
|
def register_parent(self, parent):
|
||||||
|
self._parent = parent
|
||||||
|
if parent:
|
||||||
|
self._id = f"{self._parent._id}-c-{len(self._parent._children)}"
|
||||||
|
self.data["id"] = self._id
|
||||||
|
else:
|
||||||
|
self._id = None
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
console.log("creating section")
|
||||||
|
new_child = create("section", self._id, "task bg-white my-1")
|
||||||
|
console.log("creating values")
|
||||||
|
|
||||||
|
console.log("creating innerHtml")
|
||||||
|
new_child._element.innerHTML = dedent(
|
||||||
|
f"""
|
||||||
|
<label for="flex items-center p-2 ">
|
||||||
|
<input class="mr-2" type="checkbox" class="task-check">
|
||||||
|
<p class="m-0 inline">{self.render_content()}</p>
|
||||||
|
</label>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log("returning")
|
||||||
|
return new_child
|
||||||
|
|
||||||
|
def on_click(self, evt):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pre_append(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def post_append(self):
|
||||||
|
self.element.click = self.on_click
|
||||||
|
self.element.onclick = self.on_click
|
||||||
|
|
||||||
|
self._post_append()
|
||||||
|
|
||||||
|
def _post_append(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def strike(self, value, extra=None):
|
||||||
|
if value:
|
||||||
|
self.add_class("line-through")
|
||||||
|
else:
|
||||||
|
self.remove_class("line-through")
|
||||||
|
|
||||||
|
def render_content(self):
|
||||||
|
return " - ".join([self.data[f] for f in self.labels])
|
||||||
|
|
||||||
|
|
||||||
|
class PyListTemplate:
|
||||||
|
theme = PyWidgetTheme("flex flex-col-reverse mt-8 mx-8")
|
||||||
|
item_class = PyItemTemplate
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.parent = parent
|
||||||
|
self._children = []
|
||||||
|
self._id = self.parent.id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def children(self):
|
||||||
|
return self._children
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self):
|
||||||
|
return [c.data for c in self._children]
|
||||||
|
|
||||||
|
def render_children(self):
|
||||||
|
binds = {}
|
||||||
|
for i, c in enumerate(self._children):
|
||||||
|
txt = c.element.innerHTML
|
||||||
|
rnd = str(time.time()).replace(".", "")[-5:]
|
||||||
|
new_id = f"{c.element.id}-{i}-{rnd}"
|
||||||
|
binds[new_id] = c.element.id
|
||||||
|
txt = txt.replace(">", f" id='{new_id}'>")
|
||||||
|
print(txt)
|
||||||
|
|
||||||
|
def foo(evt):
|
||||||
|
console.log(evt)
|
||||||
|
evtEl = evt.srcElement
|
||||||
|
srcEl = Element(binds[evtEl.id])
|
||||||
|
srcEl.element.onclick()
|
||||||
|
evtEl.classList = srcEl.element.classList
|
||||||
|
|
||||||
|
for new_id in binds:
|
||||||
|
Element(new_id).element.onclick = foo
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
self.md = main_div = document.createElement("div")
|
||||||
|
main_div.id = self._id + "-list-tasks-container"
|
||||||
|
|
||||||
|
if self.theme:
|
||||||
|
self.theme.theme_it(main_div)
|
||||||
|
|
||||||
|
self.parent.appendChild(main_div)
|
||||||
|
|
||||||
|
def add(self, *args, **kws):
|
||||||
|
if not isinstance(args[0], self.item_class):
|
||||||
|
child = self.item_class(*args, **kws)
|
||||||
|
else:
|
||||||
|
child = args[0]
|
||||||
|
child.register_parent(self)
|
||||||
|
return self._add(child)
|
||||||
|
|
||||||
|
def _add(self, child_elem):
|
||||||
|
console.log("appending child", child_elem.element)
|
||||||
|
self.pre_child_append(child_elem)
|
||||||
|
child_elem.pre_append()
|
||||||
|
self._children.append(child_elem)
|
||||||
|
self.md.appendChild(child_elem.create().element)
|
||||||
|
child_elem.post_append()
|
||||||
|
self.child_appended(child_elem)
|
||||||
|
return child_elem
|
||||||
|
|
||||||
|
def pre_child_append(self, child):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def child_appended(self, child):
|
||||||
|
"""Overwrite me to define logic"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
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._prev = 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.stderr = 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.stderr = self._err_manager
|
||||||
|
console.log("----> reverted")
|
||||||
|
|
||||||
|
|
||||||
|
pyscript = PyScript()
|
||||||
|
output_manager = OutputManager()
|
Binary file not shown.
@ -2,7 +2,7 @@ import datetime as dt
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from .bootstrap_HTML import *
|
from .bootstrap_HTML import *
|
||||||
from js import document, FileReader, btoa # type: ignore
|
from js import document, FileReader, btoa, Uint8Array # type: ignore
|
||||||
from pyodide import create_proxy # type: ignore
|
from pyodide import create_proxy # type: ignore
|
||||||
import io
|
import io
|
||||||
import base64
|
import base64
|
||||||
@ -323,38 +323,38 @@ class InputFile(InputElement):
|
|||||||
|
|
||||||
|
|
||||||
def _load_file(self, *args):
|
def _load_file(self, *args):
|
||||||
def read_file(event, name):
|
async def read_file(event, f):
|
||||||
buffer = io.BytesIO()
|
|
||||||
|
# FIXME: i think i am doing things too complicated here,
|
||||||
# FIXME: way too much decoding and encoding between formats here
|
# but my understanding of loading raw bytes from file
|
||||||
# but bytearray conversation between js and python drove me crazy!!!
|
# inputs in javascript (and their pyodide interacions) is very much limited
|
||||||
|
|
||||||
|
name = f.name
|
||||||
|
|
||||||
f = btoa(event.target.result)
|
print("loaded ", name)
|
||||||
buffer.write(base64.decodebytes(bytes(f, "utf-8")))
|
|
||||||
print(btoa(event.target.result))
|
uint8_array = Uint8Array.new(await f.arrayBuffer())
|
||||||
|
buffer = io.BytesIO(bytearray(uint8_array))
|
||||||
self._files[name] = buffer
|
self._files[name] = buffer
|
||||||
buffer.seek(0)
|
buffer.seek(0)
|
||||||
buffer.name = name # not standardized, but works here
|
buffer.name = name # not standardized, but works here # TODO: create base class for that purpose
|
||||||
if self._on_file_change is not None:
|
if self._on_file_change is not None:
|
||||||
self._on_file_change(buffer)
|
self._on_file_change(buffer)
|
||||||
|
|
||||||
fileList = self._input.element.files
|
file_list = self._input.element.files.to_py()
|
||||||
|
|
||||||
self._files = {}
|
self._files = {}
|
||||||
|
|
||||||
for f in fileList:
|
for f in file_list:
|
||||||
# reader is a pyodide.JsProxy
|
# reader is a pyodide.JsProxy
|
||||||
reader = FileReader.new()
|
reader = FileReader.new()
|
||||||
|
|
||||||
# Create a Python proxy for the callback function
|
# Create a Python proxy for the callback function
|
||||||
onload_event = create_proxy(lambda event, name=f.name: read_file(event, name))
|
onload_event = create_proxy(lambda event, f=f: read_file(event, f))
|
||||||
|
|
||||||
reader.onload = onload_event
|
reader.onload = onload_event
|
||||||
|
|
||||||
reader.readAsBinaryString(f)
|
reader.readAsBinaryString(f)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
|
|
||||||
|
from distutils.command.config import config
|
||||||
|
from multiprocessing.sharedctypes import Value
|
||||||
import pathlib
|
import pathlib
|
||||||
from typing import List
|
from typing import List
|
||||||
import argparse
|
import argparse
|
||||||
|
from numpy import isin
|
||||||
import requests
|
import requests
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
def download_file(url: str, path: pathlib.Path):
|
def download_file(url: str, path: pathlib.Path):
|
||||||
response = requests.get(url)
|
if pathlib.Path(url).exists():
|
||||||
path.write_bytes(response.content)
|
shutil.copy2(url, str(path))
|
||||||
|
else:
|
||||||
# create a new pyscript project
|
response = requests.get(url)
|
||||||
|
path.write_bytes(response.content)
|
||||||
|
|
||||||
|
|
||||||
def create_project(root_folder: pathlib.Path,
|
def generate_project_files(root_folder: pathlib.Path,
|
||||||
title: str,
|
title: str,
|
||||||
packages: List[str],
|
packages: List[str],
|
||||||
paths: List[str] = None,
|
paths: List[str] = None,
|
||||||
@ -78,11 +84,10 @@ btn.onclick = lambda _: bHTML.AlertSuccess(
|
|||||||
"You clicked me!", parent=app.main_area)
|
"You clicked me!", parent=app.main_area)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if root_folder.exists():
|
root_folder=pathlib.Path(root_folder)
|
||||||
raise("Value Error: root_folder already exists. Please choose a different name.")
|
|
||||||
|
|
||||||
root_folder.mkdir(parents=True)
|
root_folder.mkdir(parents=True, exist_ok=True)
|
||||||
(root_folder / "resources").mkdir(parents=True)
|
(root_folder / "resources").mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# Download files:
|
# Download files:
|
||||||
download_file(pyscript_css_url, root_folder / "resources" / "pyscript.css")
|
download_file(pyscript_css_url, root_folder / "resources" / "pyscript.css")
|
||||||
@ -94,20 +99,73 @@ btn.onclick = lambda _: bHTML.AlertSuccess(
|
|||||||
download_file(pyscript_bootstrap_templates_wheel_url,
|
download_file(pyscript_bootstrap_templates_wheel_url,
|
||||||
root_folder / "resources" / pyscript_bootstrap_templates_wheel_url.split("/")[-1])
|
root_folder / "resources" / pyscript_bootstrap_templates_wheel_url.split("/")[-1])
|
||||||
|
|
||||||
(root_folder / "index.html").write_text(html, encoding="utf-8")
|
index_html = (root_folder / "index.html")
|
||||||
(root_folder / "main.py").write_text(py, encoding="utf-8")
|
main_py = (root_folder / "main.py")
|
||||||
|
|
||||||
|
index_html.write_text(html, encoding="utf-8")
|
||||||
|
|
||||||
|
# only create if not existing:
|
||||||
|
if not main_py.exists():
|
||||||
|
main_py.write_text(py, encoding="utf-8")
|
||||||
|
|
||||||
|
def create_project(**kwargs):
|
||||||
|
|
||||||
|
root_folder: pathlib.Path = kwargs['root_folder']
|
||||||
|
|
||||||
|
if root_folder.exists():
|
||||||
|
raise ValueError(f"cannot create project. Folder {str(root_folder)} already exists")
|
||||||
|
|
||||||
|
root_folder.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# create initial config.json
|
||||||
|
|
||||||
|
kwargs['root_folder'] = str(root_folder)
|
||||||
|
|
||||||
|
with open(root_folder / "config.json", 'w') as f:
|
||||||
|
json.dump(kwargs, f, indent=4)
|
||||||
|
|
||||||
|
generate_project_files(**kwargs)
|
||||||
|
|
||||||
|
def update_project(**kwargs):
|
||||||
|
|
||||||
|
root_folder: pathlib.Path = kwargs['root_folder']
|
||||||
|
if not root_folder.exists():
|
||||||
|
raise ValueError(f"cannot update project in {str(root_folder)}. Path does not exist")
|
||||||
|
|
||||||
|
# load config file
|
||||||
|
config_json = root_folder / "config.json"
|
||||||
|
if not config_json.exists():
|
||||||
|
raise ValueError(f"cannot update project in {str(root_folder)}. Found no config.json inside give path")
|
||||||
|
|
||||||
|
with open(config_json, "r") as f:
|
||||||
|
config = json.load(f)
|
||||||
|
|
||||||
|
# override config values that are not None or empty lists
|
||||||
|
for arg, val in kwargs.items():
|
||||||
|
if val is not None:
|
||||||
|
if not isinstance(val, list) or len(val) > 0:
|
||||||
|
config[arg] = val
|
||||||
|
|
||||||
|
generate_project_files(**config)
|
||||||
|
|
||||||
|
config['root_folder'] = str(root_folder)
|
||||||
|
|
||||||
|
with open(root_folder / "config.json", 'w') as f:
|
||||||
|
json.dump(config, f, indent=4)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
argument_parser = argparse.ArgumentParser(
|
argument_parser = argparse.ArgumentParser(
|
||||||
description="create a new pyscript project")
|
description="create a new pyscript project")
|
||||||
|
|
||||||
|
argument_parser.add_argument("command", choices=["create","update"])
|
||||||
|
|
||||||
argument_parser.add_argument(
|
argument_parser.add_argument(
|
||||||
"root_folder", type=pathlib.Path, help="the root folder of the new project")
|
"root_folder", type=pathlib.Path, help="the root folder of the new project")
|
||||||
argument_parser.add_argument(
|
argument_parser.add_argument(
|
||||||
"title", type=str, help="the title of the new project")
|
"title", type=str, help="the title of the new project")
|
||||||
argument_parser.add_argument(
|
argument_parser.add_argument(
|
||||||
"packages", type=str, nargs="*", help="the packages to include in the new project")
|
"--packages", type=str, nargs="+", help="the packages to include in the new project")
|
||||||
argument_parser.add_argument("--paths", type=str, nargs="+",
|
argument_parser.add_argument("--paths", type=str, nargs="+",
|
||||||
help="additional local python files to include in the new project")
|
help="additional local python files to include in the new project")
|
||||||
argument_parser.add_argument("--pyscript_css_url", type=str,
|
argument_parser.add_argument("--pyscript_css_url", type=str,
|
||||||
@ -125,7 +183,28 @@ def main():
|
|||||||
|
|
||||||
args = argument_parser.parse_args()
|
args = argument_parser.parse_args()
|
||||||
|
|
||||||
create_project(args.root_folder, args.title, args.packages, args.paths, args.pyscript_css_url, args.pyscript_js_url, args.pyscript_py_url, args.bootstrap_css_url, args.bootstrap_js_url, args.pyscript_bootstrap_templates_wheel_url)
|
config = {
|
||||||
|
'root_folder': args.root_folder,
|
||||||
|
'title': args.title,
|
||||||
|
'packages': args.packages if args.packages is not None else [],
|
||||||
|
'paths': args.paths if args.paths is not None else [],
|
||||||
|
'pyscript_css_url': args.pyscript_css_url,
|
||||||
|
'pyscript_js_url': args.pyscript_js_url,
|
||||||
|
'pyscript_py_url': args.pyscript_py_url,
|
||||||
|
'bootstrap_css_url': args.bootstrap_css_url,
|
||||||
|
'bootstrap_js_url': args.bootstrap_js_url,
|
||||||
|
'pyscript_bootstrap_templates_wheel_url': args.pyscript_bootstrap_templates_wheel_url
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if args.command == "create":
|
||||||
|
create_project(**config)
|
||||||
|
elif args.command == "update":
|
||||||
|
update_project(**config)
|
||||||
|
else:
|
||||||
|
# should never happen and be catched by argparse#
|
||||||
|
raise ValueError("unknown command", args.command)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
Loading…
Reference in New Issue
Block a user