Compare commits
2 Commits
2ef3d3fa4f
...
01e083addb
Author | SHA1 | Date |
---|---|---|
Jonas Weinz | 01e083addb | |
Jonas Weinz | ea43b2f086 |
|
@ -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.
|
@ -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"
|
||||
}
|
|
@ -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>
|
||||
|
|
@ -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.
|
@ -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"
|
||||
}
|
|
@ -5,19 +5,18 @@
|
|||
<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">
|
||||
|
||||
<link rel="stylesheet" href="./resources/pyscript.css" />
|
||||
<script defer src="./resources/pyscript.js"></script>
|
||||
|
||||
<title>sobel_filter</title>
|
||||
<title>02_image_filter</title>
|
||||
|
||||
<py-env>
|
||||
- ./resources/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl
|
||||
- numpy
|
||||
- scikit-image
|
||||
- ./resources/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl
|
||||
|
||||
</py-env>
|
||||
</head>
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
from pyscript_bootstrap_templates import bootstrap_templates
|
||||
from pyscript_bootstrap_templates import bootstrap_HTML as bHTML
|
||||
from pyscript_bootstrap_templates import bootstrap_inputs as bInputs
|
||||
|
@ -37,5 +35,4 @@ def on_image_change(f, *args):
|
|||
output.rounded_size = 10
|
||||
output.shadow = bHTML.Shadow.LARGE
|
||||
|
||||
image_input.onchange = on_image_change
|
||||
|
||||
image_input.onchange = on_image_change
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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
|
||||
|
||||
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
|
||||
import io
|
||||
import base64
|
||||
|
@ -323,38 +323,38 @@ class InputFile(InputElement):
|
|||
|
||||
|
||||
def _load_file(self, *args):
|
||||
def read_file(event, name):
|
||||
buffer = io.BytesIO()
|
||||
|
||||
# FIXME: way too much decoding and encoding between formats here
|
||||
# but bytearray conversation between js and python drove me crazy!!!
|
||||
async def read_file(event, f):
|
||||
|
||||
# FIXME: i think i am doing things too complicated here,
|
||||
# but my understanding of loading raw bytes from file
|
||||
# inputs in javascript (and their pyodide interacions) is very much limited
|
||||
|
||||
name = f.name
|
||||
|
||||
f = btoa(event.target.result)
|
||||
buffer.write(base64.decodebytes(bytes(f, "utf-8")))
|
||||
print(btoa(event.target.result))
|
||||
print("loaded ", name)
|
||||
|
||||
uint8_array = Uint8Array.new(await f.arrayBuffer())
|
||||
buffer = io.BytesIO(bytearray(uint8_array))
|
||||
self._files[name] = buffer
|
||||
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:
|
||||
self._on_file_change(buffer)
|
||||
|
||||
fileList = self._input.element.files
|
||||
file_list = self._input.element.files.to_py()
|
||||
|
||||
self._files = {}
|
||||
|
||||
for f in fileList:
|
||||
for f in file_list:
|
||||
# reader is a pyodide.JsProxy
|
||||
reader = FileReader.new()
|
||||
|
||||
# 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.readAsBinaryString(f)
|
||||
|
||||
|
||||
|
||||
return
|
||||
|
||||
|
|
|
@ -1,18 +1,24 @@
|
|||
|
||||
from distutils.command.config import config
|
||||
from multiprocessing.sharedctypes import Value
|
||||
import pathlib
|
||||
from typing import List
|
||||
import argparse
|
||||
from numpy import isin
|
||||
import requests
|
||||
import json
|
||||
import shutil
|
||||
|
||||
|
||||
def download_file(url: str, path: pathlib.Path):
|
||||
response = requests.get(url)
|
||||
path.write_bytes(response.content)
|
||||
|
||||
# create a new pyscript project
|
||||
if pathlib.Path(url).exists():
|
||||
shutil.copy2(url, str(path))
|
||||
else:
|
||||
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,
|
||||
packages: List[str],
|
||||
paths: List[str] = None,
|
||||
|
@ -41,13 +47,12 @@ def create_project(root_folder: pathlib.Path,
|
|||
<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">
|
||||
|
||||
<link rel="stylesheet" href="./resources/pyscript.css" />
|
||||
<script defer src="./resources/pyscript.js"></script>
|
||||
|
||||
<title>{title}</title>
|
||||
|
||||
<py-env>
|
||||
|
@ -79,11 +84,10 @@ btn.onclick = lambda _: bHTML.AlertSuccess(
|
|||
"You clicked me!", parent=app.main_area)
|
||||
"""
|
||||
|
||||
if root_folder.exists():
|
||||
raise("Value Error: root_folder already exists. Please choose a different name.")
|
||||
root_folder=pathlib.Path(root_folder)
|
||||
|
||||
root_folder.mkdir(parents=True)
|
||||
(root_folder / "resources").mkdir(parents=True)
|
||||
root_folder.mkdir(parents=True, exist_ok=True)
|
||||
(root_folder / "resources").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Download files:
|
||||
download_file(pyscript_css_url, root_folder / "resources" / "pyscript.css")
|
||||
|
@ -95,20 +99,73 @@ btn.onclick = lambda _: bHTML.AlertSuccess(
|
|||
download_file(pyscript_bootstrap_templates_wheel_url,
|
||||
root_folder / "resources" / pyscript_bootstrap_templates_wheel_url.split("/")[-1])
|
||||
|
||||
(root_folder / "index.html").write_text(html, encoding="utf-8")
|
||||
(root_folder / "main.py").write_text(py, encoding="utf-8")
|
||||
index_html = (root_folder / "index.html")
|
||||
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():
|
||||
|
||||
argument_parser = argparse.ArgumentParser(
|
||||
description="create a new pyscript project")
|
||||
|
||||
argument_parser.add_argument("command", choices=["create","update"])
|
||||
|
||||
argument_parser.add_argument(
|
||||
"root_folder", type=pathlib.Path, help="the root folder of the new project")
|
||||
argument_parser.add_argument(
|
||||
"title", type=str, help="the title of the new project")
|
||||
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="+",
|
||||
help="additional local python files to include in the new project")
|
||||
argument_parser.add_argument("--pyscript_css_url", type=str,
|
||||
|
@ -126,7 +183,28 @@ def main():
|
|||
|
||||
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__":
|
||||
main()
|
Loading…
Reference in New Issue