new advanced example, small fixes
This commit is contained in:
7
examples/04_cell_detector/resources/bootstrap.css
vendored
Normal file
7
examples/04_cell_detector/resources/bootstrap.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
examples/04_cell_detector/resources/bootstrap.js
vendored
Normal file
7
examples/04_cell_detector/resources/bootstrap.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
examples/04_cell_detector/resources/icon-512x512.png
Normal file
BIN
examples/04_cell_detector/resources/icon-512x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
10
examples/04_cell_detector/resources/pwa.js
Normal file
10
examples/04_cell_detector/resources/pwa.js
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", function () {
|
||||
navigator.serviceWorker
|
||||
.register("./serviceWorker.js")
|
||||
.then(res => console.log("service worker registered", res))
|
||||
.catch(err => console.log("service worker not registered", err))
|
||||
})
|
||||
}
|
||||
|
332
examples/04_cell_detector/resources/pyscript.css
Normal file
332
examples/04_cell_detector/resources/pyscript.css
Normal file
@ -0,0 +1,332 @@
|
||||
/* py-config - not a component */
|
||||
py-config {
|
||||
display: none;
|
||||
}
|
||||
/* py-{el} - components not defined */
|
||||
py-script:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-repl:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-title:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-inputbox:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-button:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-box:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
|
||||
Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.spinner::after {
|
||||
content: '';
|
||||
box-sizing: border-box;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
position: absolute;
|
||||
top: calc(40% - 20px);
|
||||
left: calc(50% - 20px);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.spinner.smooth::after {
|
||||
border-top: 4px solid rgba(255, 255, 255, 1);
|
||||
border-left: 4px solid rgba(255, 255, 255, 1);
|
||||
border-right: 4px solid rgba(255, 255, 255, 0);
|
||||
animation: spinner 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spinner {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
display: block;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 0.8rem;
|
||||
margin-top: 6rem;
|
||||
}
|
||||
|
||||
/* Pop-up second layer begin */
|
||||
|
||||
.py-overlay {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
transition: opacity 500ms;
|
||||
visibility: hidden;
|
||||
color: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.py-overlay {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.py-pop-up {
|
||||
text-align: center;
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.py-pop-up p {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.py-pop-up a {
|
||||
position: absolute;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 200%;
|
||||
top: 3.5%;
|
||||
right: 5%;
|
||||
}
|
||||
|
||||
/* Pop-up second layer end */
|
||||
.alert-banner {
|
||||
position: relative;
|
||||
padding: 0.5rem 1.5rem 0.5rem 0.5rem;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.alert-banner p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.py-error {
|
||||
background-color: #ffe9e8;
|
||||
border: solid;
|
||||
border-color: #f0625f;
|
||||
color: #9d041c;
|
||||
}
|
||||
|
||||
.py-warning {
|
||||
background-color: rgb(255, 244, 229);
|
||||
border: solid;
|
||||
border-color: #ffa016;
|
||||
color: #794700;
|
||||
}
|
||||
|
||||
.alert-banner.py-error > #alert-close-button {
|
||||
color: #9d041c;
|
||||
}
|
||||
|
||||
.alert-banner.py-warning > #alert-close-button {
|
||||
color: #794700;
|
||||
}
|
||||
|
||||
#alert-close-button {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
top: 0.5rem;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.py-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.py-box div.py-box-child * {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.py-repl-box {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.py-repl-editor {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgba(209, 213, 219, var(--tw-border-opacity));
|
||||
border-width: 1px;
|
||||
position: relative;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
position: relative;
|
||||
|
||||
box-sizing: border-box;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: rgb(209, 213, 219);
|
||||
}
|
||||
|
||||
.editor-box:hover button {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.py-repl-run-button {
|
||||
opacity: 0;
|
||||
bottom: 0.25rem;
|
||||
right: 0.25rem;
|
||||
position: absolute;
|
||||
padding: 0;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
-webkit-appearance: button;
|
||||
text-transform: none;
|
||||
font-family: inherit;
|
||||
font-size: 100%;
|
||||
margin: 0;
|
||||
text-rendering: auto;
|
||||
letter-spacing: normal;
|
||||
word-spacing: normal;
|
||||
line-height: normal;
|
||||
text-transform: none;
|
||||
text-indent: 0px;
|
||||
text-shadow: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
align-items: flex-start;
|
||||
cursor: default;
|
||||
box-sizing: border-box;
|
||||
background-color: -internal-light-dark(rgb(239, 239, 239), rgb(59, 59, 59));
|
||||
margin: 0em;
|
||||
padding: 1px 6px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.py-repl-run-button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.py-title {
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.py-title h1 {
|
||||
font-weight: 700;
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
|
||||
.py-input {
|
||||
padding: 0.5rem;
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgba(209, 213, 219, var(--tw-border-opacity));
|
||||
border-width: 1px;
|
||||
border-radius: 0.25rem;
|
||||
margin-right: 0.75rem;
|
||||
border-style: solid;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.py-box input.py-input {
|
||||
width: -webkit-fill-available;
|
||||
}
|
||||
|
||||
.central-content {
|
||||
max-width: 20rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
input {
|
||||
text-rendering: auto;
|
||||
color: -internal-light-dark(black, white);
|
||||
letter-spacing: normal;
|
||||
word-spacing: normal;
|
||||
line-height: normal;
|
||||
text-transform: none;
|
||||
text-indent: 0px;
|
||||
text-shadow: none;
|
||||
display: inline-block;
|
||||
text-align: start;
|
||||
appearance: auto;
|
||||
-webkit-rtl-ordering: logical;
|
||||
background-color: -internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59));
|
||||
margin: 0em;
|
||||
padding: 1px 2px;
|
||||
border-width: 2px;
|
||||
border-style: inset;
|
||||
border-color: -internal-light-dark(rgb(118, 118, 118), rgb(133, 133, 133));
|
||||
border-image: initial;
|
||||
}
|
||||
|
||||
.py-button {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgba(255, 255, 255, var(--tw-text-opacity));
|
||||
padding: 0.5rem;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(37, 99, 235, var(--tw-bg-opacity));
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgba(37, 99, 235, var(--tw-border-opacity));
|
||||
border-width: 1px;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.py-li-element p {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.py-li-element p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
font-size: 100%;
|
||||
line-height: 1.15;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.line-through {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* ===== py-terminal plugin ===== */
|
||||
/* XXX: it would be nice if these rules were stored in e.g. pyterminal.css and
|
||||
bundled together at build time (by rollup?) */
|
||||
|
||||
.py-terminal {
|
||||
min-height: 10em;
|
||||
background-color: black;
|
||||
color: white;
|
||||
padding: 0.5rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.py-terminal-hidden {
|
||||
display: none;
|
||||
}
|
28907
examples/04_cell_detector/resources/pyscript.js
Normal file
28907
examples/04_cell_detector/resources/pyscript.js
Normal file
File diff suppressed because one or more lines are too long
424
examples/04_cell_detector/resources/pyscript.py
Normal file
424
examples/04_cell_detector/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.
Binary file not shown.
Reference in New Issue
Block a user