mirror of
https://github.com/pyscript/pyscript.git
synced 2022-05-01 19:47:48 +03:00
Merge pull request #36 from anaconda/pys-18/add-pylist
[PYS-18] Add pylist
This commit is contained in:
22
pyscriptjs/examples/pylist.py
Normal file
22
pyscriptjs/examples/pylist.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from datetime import datetime as dt
|
||||
|
||||
class PyItem(PyItemTemplate):
|
||||
def on_click(self, evt=None):
|
||||
self.data['done'] = not self.data['done']
|
||||
self.strike(self.data['done'])
|
||||
|
||||
self.select('input').element.checked = self.data['done']
|
||||
|
||||
class PyList(PyListTemplate):
|
||||
item_class = PyItem
|
||||
|
||||
def add_task(*ags, **kws):
|
||||
# create a new dictionary representing the new task
|
||||
task = { "content": new_task_content.value, "done": False, "created_at": dt.now() }
|
||||
|
||||
# add a new task to the list and tell it to use the `content` key to show in the UI
|
||||
# and to use the key `done` to sync the task status with a checkbox element in the UI
|
||||
myList.add(task, labels=['content'], state_key="done")
|
||||
|
||||
# clear the inputbox element used to create the new task
|
||||
new_task_content.clear()
|
||||
36
pyscriptjs/examples/todo-pylist.html
Normal file
36
pyscriptjs/examples/todo-pylist.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Todo App</title>
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link rel="stylesheet" href="/build/pyscript.css" />
|
||||
|
||||
<script defer src="/build/pyscript.js"></script>
|
||||
<py-env>
|
||||
- paths:
|
||||
- /utils.py
|
||||
</py-env>
|
||||
<py-register-widget src="/pylist.py" name="py-list" klass="PyList"></py-register-widget>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-title>To Do List</py-title>
|
||||
<py-box widths="4/5;1/5">
|
||||
<py-inputbox id="new-task-content">
|
||||
def on_keypress(e):
|
||||
if (e.code == "Enter"):
|
||||
add_task()
|
||||
</py-inputbox>
|
||||
<py-button id="new-task-btn" label="Add Task!">
|
||||
def on_click(evt):
|
||||
add_task()
|
||||
</button>
|
||||
</py-box>
|
||||
|
||||
<py-list id="myList"></py-list>
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
</body>
|
||||
</html>
|
||||
@@ -53,7 +53,7 @@
|
||||
for (let initializer of $postInitializers){
|
||||
initializer();
|
||||
}
|
||||
}, 5000);
|
||||
}, 3000);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -149,5 +149,174 @@ export class BaseEvalElement extends HTMLElement {
|
||||
this.errorElement.hidden = false;
|
||||
this.errorElement.style.display = 'block';
|
||||
}
|
||||
}
|
||||
} // end evaluate
|
||||
|
||||
async eval(source: string): Promise<void> {
|
||||
let output;
|
||||
let pyodide = await pyodideReadyPromise;
|
||||
|
||||
try{
|
||||
output = await pyodide.runPythonAsync(source);
|
||||
if (output !== undefined){ console.log(output); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
} // end eval
|
||||
}
|
||||
|
||||
function createWidget(name: string, code: string, klass: string){
|
||||
|
||||
class CustomWidget extends HTMLElement{
|
||||
shadow: ShadowRoot;
|
||||
wrapper: HTMLElement;
|
||||
|
||||
name: string = name;
|
||||
klass: string = klass;
|
||||
code: string = code;
|
||||
proxy: any;
|
||||
proxyClass: any;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// attach shadow so we can preserve the element original innerHtml content
|
||||
this.shadow = this.attachShadow({ mode: 'open'});
|
||||
|
||||
this.wrapper = document.createElement('slot');
|
||||
this.shadow.appendChild(this.wrapper);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
// TODO: we are calling with a 2secs delay to allow pyodide to load
|
||||
// ideally we can just wait for it to load and then run. To do
|
||||
// so we need to replace using the promise and actually using
|
||||
// the interpreter after it loads completely
|
||||
setTimeout(() => {
|
||||
this.eval(this.code).then(() => {
|
||||
this.proxy = this.proxyClass(this);
|
||||
console.log('proxy', this.proxy);
|
||||
this.proxy.connect();
|
||||
this.registerWidget();
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
async registerWidget(){
|
||||
let pyodide = await pyodideReadyPromise;
|
||||
console.log('new widget registered:', this.name);
|
||||
pyodide.globals.set(this.id, this.proxy);
|
||||
}
|
||||
|
||||
async eval(source: string): Promise<void> {
|
||||
let output;
|
||||
let pyodide = await pyodideReadyPromise;
|
||||
try{
|
||||
output = await pyodide.runPythonAsync(source);
|
||||
this.proxyClass = pyodide.globals.get(this.klass);
|
||||
if (output !== undefined){
|
||||
console.log(output);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
let xPyWidget = customElements.define(name, CustomWidget);
|
||||
}
|
||||
|
||||
export class PyWidget extends HTMLElement {
|
||||
shadow: ShadowRoot;
|
||||
name: string;
|
||||
klass: string;
|
||||
outputElement: HTMLElement;
|
||||
errorElement: HTMLElement;
|
||||
wrapper: HTMLElement;
|
||||
theme: string;
|
||||
source: string;
|
||||
code: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// attach shadow so we can preserve the element original innerHtml content
|
||||
this.shadow = this.attachShadow({ mode: 'open'});
|
||||
|
||||
this.wrapper = document.createElement('slot');
|
||||
this.shadow.appendChild(this.wrapper);
|
||||
|
||||
if (this.hasAttribute('src')) {
|
||||
this.source = this.getAttribute('src');
|
||||
}
|
||||
|
||||
if (this.hasAttribute('name')) {
|
||||
this.name = this.getAttribute('name');
|
||||
}
|
||||
|
||||
if (this.hasAttribute('klass')) {
|
||||
this.klass = this.getAttribute('klass');
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (this.id === undefined){
|
||||
throw new ReferenceError(`No id specified for component. Components must have an explicit id. Please use id="" to specify your component id.`)
|
||||
return;
|
||||
}
|
||||
|
||||
let mainDiv = document.createElement('div');
|
||||
mainDiv.id = this.id + '-main';
|
||||
this.appendChild(mainDiv);
|
||||
console.log('reading source')
|
||||
this.getSourceFromFile(this.source).then((code:string) => {
|
||||
this.code = code;
|
||||
createWidget(this.name, code, this.klass);
|
||||
});
|
||||
}
|
||||
|
||||
initOutErr(): void {
|
||||
if (this.hasAttribute('output')) {
|
||||
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
|
||||
|
||||
// in this case, the default output-mode is append, if hasn't been specified
|
||||
if (!this.hasAttribute('output-mode')) {
|
||||
this.setAttribute('output-mode', 'append');
|
||||
}
|
||||
}else{
|
||||
if (this.hasAttribute('std-out')){
|
||||
this.outputElement = document.getElementById(this.getAttribute('std-out'));
|
||||
}else{
|
||||
// In this case neither output or std-out have been provided so we need
|
||||
// to create a new output div to output to
|
||||
this.outputElement = document.createElement('div');
|
||||
this.outputElement.classList.add("output");
|
||||
this.outputElement.hidden = true;
|
||||
this.outputElement.id = this.id + "-" + this.getAttribute("exec-id");
|
||||
}
|
||||
|
||||
if (this.hasAttribute('std-err')){
|
||||
this.outputElement = document.getElementById(this.getAttribute('std-err'));
|
||||
}else{
|
||||
this.errorElement = this.outputElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getSourceFromFile(s: string): Promise<string>{
|
||||
let pyodide = await pyodideReadyPromise;
|
||||
let response = await fetch(s);
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
async eval(source: string): Promise<void> {
|
||||
let output;
|
||||
let pyodide = await pyodideReadyPromise;
|
||||
try{
|
||||
output = await pyodide.runPythonAsync(source);
|
||||
if (output !== undefined){
|
||||
console.log(output);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export class PyBox extends HTMLElement {
|
||||
|
||||
connectedCallback() {
|
||||
let mainDiv = document.createElement('div');
|
||||
addClasses(mainDiv, ["flex"])
|
||||
addClasses(mainDiv, ["flex", "mx-8"])
|
||||
|
||||
// Hack: for some reason when moving children, the editor box duplicates children
|
||||
// meaning that we end up with 2 editors, if there's a <py-repl> inside the <py-box>
|
||||
|
||||
56
pyscriptjs/src/components/pybutton.ts
Normal file
56
pyscriptjs/src/components/pybutton.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { BaseEvalElement } from './base';
|
||||
import { addClasses, ltrim, htmlDecode } from '../utils';
|
||||
|
||||
export class PyButton extends BaseEvalElement {
|
||||
shadow: ShadowRoot;
|
||||
wrapper: HTMLElement;
|
||||
theme: string;
|
||||
widths: Array<string>;
|
||||
label: string;
|
||||
mount_name: string;
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (this.hasAttribute('label')) {
|
||||
this.label = this.getAttribute('label');
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.code = htmlDecode(this.innerHTML);
|
||||
this.mount_name = this.id.split("-").join("_");
|
||||
this.innerHTML = '';
|
||||
|
||||
let mainDiv = document.createElement('button');
|
||||
mainDiv.innerHTML = this.label;
|
||||
addClasses(mainDiv, ["p-2", "text-white", "bg-blue-600", "border", "border-blue-600", "rounded"]);
|
||||
|
||||
mainDiv.id = this.id;
|
||||
this.id = `${this.id}-container`;
|
||||
|
||||
this.appendChild(mainDiv);
|
||||
this.code = this.code.split("self").join(this.mount_name);
|
||||
let registrationCode = `${this.mount_name} = Element("${ mainDiv.id }")`;
|
||||
if (this.code.includes("def on_focus")){
|
||||
this.code = this.code.replace("def on_focus", `def on_focus_${this.mount_name}`);
|
||||
registrationCode += `\n${this.mount_name}.element.onfocus = on_focus_${this.mount_name}`
|
||||
}
|
||||
|
||||
if (this.code.includes("def on_click")){
|
||||
this.code = this.code.replace("def on_click", `def on_click_${this.mount_name}`);
|
||||
registrationCode += `\n${this.mount_name}.element.onclick = on_click_${this.mount_name}`
|
||||
}
|
||||
|
||||
// now that we appended and the element is attached, lets connect with the event handlers
|
||||
// defined for this widget
|
||||
setTimeout(() => {
|
||||
this.eval(this.code).then(() => {
|
||||
this.eval(registrationCode).then(() => {
|
||||
console.log('registered handlers');
|
||||
});
|
||||
});
|
||||
}, 4000);
|
||||
|
||||
console.log('py-button connected');
|
||||
}
|
||||
}
|
||||
54
pyscriptjs/src/components/pyinputbox.ts
Normal file
54
pyscriptjs/src/components/pyinputbox.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { BaseEvalElement } from './base';
|
||||
import { addClasses, ltrim, htmlDecode } from '../utils';
|
||||
|
||||
export class PyInputBox extends BaseEvalElement {
|
||||
shadow: ShadowRoot;
|
||||
wrapper: HTMLElement;
|
||||
theme: string;
|
||||
widths: Array<string>;
|
||||
label: string;
|
||||
mount_name: string;
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (this.hasAttribute('label')) {
|
||||
this.label = this.getAttribute('label');
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.code = htmlDecode(this.innerHTML);
|
||||
this.mount_name = this.id.split("-").join("_");
|
||||
this.innerHTML = '';
|
||||
|
||||
let mainDiv = document.createElement('input');
|
||||
mainDiv.type = "text";
|
||||
addClasses(mainDiv, ["border", "flex-1", "w-full", "mr-3", "border-gray-300", "p-2", "rounded"]);
|
||||
|
||||
mainDiv.id = this.id;
|
||||
this.id = `${this.id}-container`;
|
||||
this.appendChild(mainDiv);
|
||||
|
||||
// now that we appended and the element is attached, lets connect with the event handlers
|
||||
// defined for this widget
|
||||
this.appendChild(mainDiv);
|
||||
this.code = this.code.split("self").join(this.mount_name);
|
||||
let registrationCode = `${this.mount_name} = Element("${ mainDiv.id }")`;
|
||||
if (this.code.includes("def on_keypress")){
|
||||
this.code = this.code.replace("def on_keypress", `def on_keypress_${this.mount_name}`);
|
||||
registrationCode += `\n${this.mount_name}.element.onkeypress = on_keypress_${this.mount_name}`
|
||||
}
|
||||
|
||||
// TODO: For now we delay execution to allow pyodide to load but in the future this
|
||||
// should really wait for it to load..
|
||||
setTimeout(() => {
|
||||
this.eval(this.code).then(() => {
|
||||
this.eval(registrationCode).then(() => {
|
||||
console.log('registered handlers');
|
||||
});
|
||||
});
|
||||
}, 4000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ export class PyRepl extends BaseEvalElement {
|
||||
})
|
||||
|
||||
let mainDiv = document.createElement('div');
|
||||
addClasses(mainDiv, ["parentBox", "group", "flex", "flex-col", "mt-2", "border-2", "border-gray-200", "rounded-lg"])
|
||||
addClasses(mainDiv, ["parentBox", "group", "flex", "flex-col", "mt-2", "border-2", "border-gray-200", "rounded-lg", "mx-8"])
|
||||
// add Editor to main PyScript div
|
||||
|
||||
// Butons DIV
|
||||
@@ -199,6 +199,10 @@ export class PyRepl extends BaseEvalElement {
|
||||
}
|
||||
|
||||
postEvaluate(): void {
|
||||
|
||||
this.outputElement.hidden = false;
|
||||
this.outputElement.style.display = 'block';
|
||||
|
||||
if (this.hasAttribute('auto-generate')) {
|
||||
let nextExecId = parseInt(this.getAttribute('exec-id')) + 1;
|
||||
const newPyRepl = document.createElement("py-repl");
|
||||
|
||||
@@ -7,7 +7,7 @@ import { defaultKeymap } from "@codemirror/commands";
|
||||
import { oneDarkTheme } from "@codemirror/theme-one-dark";
|
||||
|
||||
import { pyodideLoaded, loadedEnvironments, componentDetailsNavOpen, currentComponentDetails, mode, addToScriptsQueue, addInitializer, addPostInitializer } from '../stores';
|
||||
import { addClasses } from '../utils';
|
||||
import { addClasses, htmlDecode } from '../utils';
|
||||
import { BaseEvalElement } from './base';
|
||||
|
||||
// Premise used to connect to the first available pyodide interpreter
|
||||
@@ -41,11 +41,6 @@ function createCmdHandler(el){
|
||||
return toggleCheckbox
|
||||
}
|
||||
|
||||
function htmlDecode(input) {
|
||||
var doc = new DOMParser().parseFromString(input, "text/html");
|
||||
return doc.documentElement.textContent;
|
||||
}
|
||||
|
||||
// TODO: use type declaractions
|
||||
type PyodideInterface = {
|
||||
registerJsModule(name: string, module: object): void
|
||||
@@ -296,7 +291,7 @@ async function mountElements() {
|
||||
for (var el of matches) {
|
||||
let mountName = el.getAttribute('py-mount');
|
||||
if (!mountName){
|
||||
mountName = el.id.replace("-", "_");
|
||||
mountName = el.id.split("-").join("_");
|
||||
}
|
||||
source += `\n${ mountName } = Element("${ el.id }")`;
|
||||
}
|
||||
|
||||
34
pyscriptjs/src/components/pytitle.ts
Normal file
34
pyscriptjs/src/components/pytitle.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { BaseEvalElement } from './base';
|
||||
import { addClasses, ltrim, htmlDecode } from '../utils';
|
||||
|
||||
export class PyTitle extends BaseEvalElement {
|
||||
shadow: ShadowRoot;
|
||||
wrapper: HTMLElement;
|
||||
theme: string;
|
||||
widths: Array<string>;
|
||||
label: string;
|
||||
mount_name: string;
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.label = htmlDecode(this.innerHTML);
|
||||
this.mount_name = this.id.split("-").join("_");
|
||||
this.innerHTML = '';
|
||||
|
||||
let mainDiv = document.createElement('div');
|
||||
let divContent = document.createElement('h1')
|
||||
|
||||
addClasses(mainDiv, ["text-center", "w-full", "mb-8"]);
|
||||
addClasses(divContent, ["text-3xl", "font-bold", "text-gray-800", "uppercase", "tracking-tight"]);
|
||||
divContent.innerHTML = this.label;
|
||||
|
||||
mainDiv.id = this.id;
|
||||
this.id = `${this.id}-container`;
|
||||
mainDiv.appendChild(divContent);
|
||||
this.appendChild(mainDiv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,9 @@ let pyodideReadyPromise;
|
||||
let pyodide;
|
||||
|
||||
let additional_definitions = `
|
||||
from js import document, setInterval, console
|
||||
from js import document, setInterval, console, setTimeout
|
||||
import micropip
|
||||
import time
|
||||
import asyncio
|
||||
import io, base64, sys
|
||||
|
||||
@@ -51,6 +52,10 @@ class Element:
|
||||
self._id = element_id
|
||||
self._element = element
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def element(self):
|
||||
"""Return the dom element"""
|
||||
@@ -58,6 +63,14 @@ class 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
|
||||
@@ -96,6 +109,176 @@ class Element:
|
||||
|
||||
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 = 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):
|
||||
out = []
|
||||
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, old_id in binds.items():
|
||||
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
|
||||
|
||||
@@ -4,12 +4,19 @@ import { PyScript } from "./components/pyscript";
|
||||
import { PyRepl } from "./components/pyrepl";
|
||||
import { PyEnv } from "./components/pyenv";
|
||||
import { PyBox } from "./components/pybox";
|
||||
|
||||
import { PyButton } from "./components/pybutton";
|
||||
import { PyTitle } from "./components/pytitle";
|
||||
import { PyInputBox } from "./components/pyinputbox";
|
||||
import { PyWidget } from "./components/base";
|
||||
|
||||
let xPyScript = customElements.define('py-script', PyScript);
|
||||
let xPyRepl = customElements.define('py-repl', PyRepl);
|
||||
let xPyEnv = customElements.define('py-env', PyEnv);
|
||||
let xPyBox = customElements.define('py-box', PyBox);
|
||||
let xPyButton = customElements.define('py-button', PyButton);
|
||||
let xPyTitle = customElements.define('py-title', PyTitle);
|
||||
let xPyInputBox = customElements.define('py-inputbox', PyInputBox);
|
||||
let xPyWidget = customElements.define('py-register-widget', PyWidget);
|
||||
|
||||
|
||||
const app = new App({
|
||||
|
||||
@@ -9,4 +9,29 @@ const getLastPath = function (str) {
|
||||
return str.split('\\').pop().split('/').pop();
|
||||
}
|
||||
|
||||
export {addClasses, getLastPath}
|
||||
function htmlDecode(input) {
|
||||
var doc = new DOMParser().parseFromString(input, "text/html");
|
||||
return ltrim(doc.documentElement.textContent);
|
||||
}
|
||||
|
||||
function ltrim(code: string): string {
|
||||
const lines = code.split("\n")
|
||||
if (lines.length == 0)
|
||||
return code
|
||||
|
||||
const lengths = lines
|
||||
.filter((line) => line.trim().length != 0)
|
||||
.map((line) => {
|
||||
const [prefix] = line.match(/^\s*/)
|
||||
return prefix.length
|
||||
})
|
||||
|
||||
const k = Math.min(...lengths)
|
||||
|
||||
if (k != 0)
|
||||
return lines.map((line) => line.substring(k)).join("\n")
|
||||
else
|
||||
return code
|
||||
}
|
||||
|
||||
export {addClasses, getLastPath, ltrim, htmlDecode}
|
||||
|
||||
Reference in New Issue
Block a user