HTeaLeaf 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- htealeaf-0.3.0/PKG-INFO +95 -0
- htealeaf-0.3.0/README.md +86 -0
- htealeaf-0.3.0/pyproject.toml +17 -0
- htealeaf-0.3.0/setup.cfg +4 -0
- htealeaf-0.3.0/src/HTeaLeaf/Html/Component.py +219 -0
- htealeaf-0.3.0/src/HTeaLeaf/Html/Elements.py +137 -0
- htealeaf-0.3.0/src/HTeaLeaf/Html/__init__.py +2 -0
- htealeaf-0.3.0/src/HTeaLeaf/Magic/HelperMidleware.py +13 -0
- htealeaf-0.3.0/src/HTeaLeaf/Magic/LocalState.py +38 -0
- htealeaf-0.3.0/src/HTeaLeaf/Magic/MagicComponent.py +122 -0
- htealeaf-0.3.0/src/HTeaLeaf/Magic/Store.py +180 -0
- htealeaf-0.3.0/src/HTeaLeaf/Magic/__init__.py +0 -0
- htealeaf-0.3.0/src/HTeaLeaf/Magic/jslib/JSCode.py +110 -0
- htealeaf-0.3.0/src/HTeaLeaf/Magic/jslib/JSDO.py +63 -0
- htealeaf-0.3.0/src/HTeaLeaf/Magic/jslib/__init__.py +2 -0
- htealeaf-0.3.0/src/HTeaLeaf/Magic/jslib/common.py +43 -0
- htealeaf-0.3.0/src/HTeaLeaf/Magic/jslib/py2js.py +307 -0
- htealeaf-0.3.0/src/HTeaLeaf/Server/ASGI.py +80 -0
- htealeaf-0.3.0/src/HTeaLeaf/Server/CGI.py +46 -0
- htealeaf-0.3.0/src/HTeaLeaf/Server/Http/HttpHeader.py +40 -0
- htealeaf-0.3.0/src/HTeaLeaf/Server/Http/HttpRequest.py +81 -0
- htealeaf-0.3.0/src/HTeaLeaf/Server/Http/HttpResponse.py +75 -0
- htealeaf-0.3.0/src/HTeaLeaf/Server/Http/__init__.py +0 -0
- htealeaf-0.3.0/src/HTeaLeaf/Server/Server.py +236 -0
- htealeaf-0.3.0/src/HTeaLeaf/Server/WSGI.py +37 -0
- htealeaf-0.3.0/src/HTeaLeaf/Server/__init__.py +0 -0
- htealeaf-0.3.0/src/HTeaLeaf/__init__.py +0 -0
- htealeaf-0.3.0/src/HTeaLeaf/utils.py +3 -0
- htealeaf-0.3.0/src/HTeaLeaf.egg-info/PKG-INFO +95 -0
- htealeaf-0.3.0/src/HTeaLeaf.egg-info/SOURCES.txt +31 -0
- htealeaf-0.3.0/src/HTeaLeaf.egg-info/dependency_links.txt +1 -0
- htealeaf-0.3.0/src/HTeaLeaf.egg-info/requires.txt +2 -0
- htealeaf-0.3.0/src/HTeaLeaf.egg-info/top_level.txt +1 -0
htealeaf-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: HTeaLeaf
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Declarative SSR Framework
|
|
5
|
+
Requires-Python: >=3.13
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: gunicorn>=23.0.0
|
|
8
|
+
Requires-Dist: uvicorn>=0.38.0
|
|
9
|
+
|
|
10
|
+
# π TeaLeaf
|
|
11
|
+
|
|
12
|
+
**TeaLeaf** is a *declarative web framework for Python* β
|
|
13
|
+
it lets you build dynamic, reactive web apps using **pure Python**,
|
|
14
|
+
without writing templates or frontend JavaScript manually.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## β¨ Overview
|
|
19
|
+
|
|
20
|
+
TeaLeaf merges ideas from modern frontend frameworks like React, Svelte, and SolidJS
|
|
21
|
+
with the simplicity of traditional Python web servers.
|
|
22
|
+
|
|
23
|
+
You declare HTML directly in Python, manage reactive state via `Store` objects,
|
|
24
|
+
and TeaLeaf takes care of keeping everything in sync β automatically.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## π Quick Example
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from TeaLeaf.Server.WSGI import WSGI
|
|
32
|
+
from TeaLeaf.Magic.Store import Store, SuperStore
|
|
33
|
+
from TeaLeaf.Html.Elements import div, h3, button
|
|
34
|
+
|
|
35
|
+
# Create the server
|
|
36
|
+
app = WSGI()
|
|
37
|
+
SuperStore(app)
|
|
38
|
+
|
|
39
|
+
# Reactive server-side store
|
|
40
|
+
counter = Store({"count": 0})
|
|
41
|
+
|
|
42
|
+
@app.route("/")
|
|
43
|
+
def home():
|
|
44
|
+
return div(
|
|
45
|
+
button("-").attr(onclick=counter.do.update("count", -1)),
|
|
46
|
+
h3(counter.react("count")),
|
|
47
|
+
button("+").attr(onclick=counter.do.update("count", 1)),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
application = app.wsgi_app
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
from wsgiref.simple_server import make_server
|
|
54
|
+
with make_server("", 8000, application) as server:
|
|
55
|
+
print("Serving at http://127.0.0.1:8000")
|
|
56
|
+
server.serve_forever()
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Open your browser and visit http://127.0.0.1:8000 β
|
|
60
|
+
youβll see a fully reactive counter built with only Python
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## Key Features
|
|
64
|
+
- Declarative HTML components β build DOM structures with Python functions
|
|
65
|
+
- Path-based routing β simple, expressive route definitions
|
|
66
|
+
- Reactive server state (Store, AuthStore) β auto-sync between backend and UI
|
|
67
|
+
- JS transpilation (JSCode, JSDO) β write JavaScript logic directly in Python
|
|
68
|
+
- Session support β cookies and per-user AuthStore state
|
|
69
|
+
|
|
70
|
+
## Roadmap
|
|
71
|
+
|
|
72
|
+
- [x] Declarative HTML components
|
|
73
|
+
- [x] Path mapping
|
|
74
|
+
- [x] Server Side State (Stores)
|
|
75
|
+
- [x] JS transcription from python
|
|
76
|
+
- [ ] Client Side state
|
|
77
|
+
- [ ] State hooks
|
|
78
|
+
- [ ] Template system
|
|
79
|
+
- [ ] Persistent Store (redis,SQL...)
|
|
80
|
+
- [ ] CLI
|
|
81
|
+
- [ ] Render optimisation
|
|
82
|
+
|
|
83
|
+
Documentation
|
|
84
|
+
|
|
85
|
+
Full documentation is available in the [Wiki](https://github.com/Az107/TeaLeaf/wiki/Welcome-to-the-TeaLeaf!)
|
|
86
|
+
|
|
87
|
+
## Status
|
|
88
|
+
|
|
89
|
+
TeaLeaf is currently in alpha.
|
|
90
|
+
Itβs stable enough for experimentation and small demos,
|
|
91
|
+
but the public API might still change before beta.
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT License Β© 2025 β TeaLeaf Framework Made with π and Python.
|
htealeaf-0.3.0/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# π TeaLeaf
|
|
2
|
+
|
|
3
|
+
**TeaLeaf** is a *declarative web framework for Python* β
|
|
4
|
+
it lets you build dynamic, reactive web apps using **pure Python**,
|
|
5
|
+
without writing templates or frontend JavaScript manually.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## β¨ Overview
|
|
10
|
+
|
|
11
|
+
TeaLeaf merges ideas from modern frontend frameworks like React, Svelte, and SolidJS
|
|
12
|
+
with the simplicity of traditional Python web servers.
|
|
13
|
+
|
|
14
|
+
You declare HTML directly in Python, manage reactive state via `Store` objects,
|
|
15
|
+
and TeaLeaf takes care of keeping everything in sync β automatically.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## π Quick Example
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from TeaLeaf.Server.WSGI import WSGI
|
|
23
|
+
from TeaLeaf.Magic.Store import Store, SuperStore
|
|
24
|
+
from TeaLeaf.Html.Elements import div, h3, button
|
|
25
|
+
|
|
26
|
+
# Create the server
|
|
27
|
+
app = WSGI()
|
|
28
|
+
SuperStore(app)
|
|
29
|
+
|
|
30
|
+
# Reactive server-side store
|
|
31
|
+
counter = Store({"count": 0})
|
|
32
|
+
|
|
33
|
+
@app.route("/")
|
|
34
|
+
def home():
|
|
35
|
+
return div(
|
|
36
|
+
button("-").attr(onclick=counter.do.update("count", -1)),
|
|
37
|
+
h3(counter.react("count")),
|
|
38
|
+
button("+").attr(onclick=counter.do.update("count", 1)),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
application = app.wsgi_app
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
from wsgiref.simple_server import make_server
|
|
45
|
+
with make_server("", 8000, application) as server:
|
|
46
|
+
print("Serving at http://127.0.0.1:8000")
|
|
47
|
+
server.serve_forever()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Open your browser and visit http://127.0.0.1:8000 β
|
|
51
|
+
youβll see a fully reactive counter built with only Python
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## Key Features
|
|
55
|
+
- Declarative HTML components β build DOM structures with Python functions
|
|
56
|
+
- Path-based routing β simple, expressive route definitions
|
|
57
|
+
- Reactive server state (Store, AuthStore) β auto-sync between backend and UI
|
|
58
|
+
- JS transpilation (JSCode, JSDO) β write JavaScript logic directly in Python
|
|
59
|
+
- Session support β cookies and per-user AuthStore state
|
|
60
|
+
|
|
61
|
+
## Roadmap
|
|
62
|
+
|
|
63
|
+
- [x] Declarative HTML components
|
|
64
|
+
- [x] Path mapping
|
|
65
|
+
- [x] Server Side State (Stores)
|
|
66
|
+
- [x] JS transcription from python
|
|
67
|
+
- [ ] Client Side state
|
|
68
|
+
- [ ] State hooks
|
|
69
|
+
- [ ] Template system
|
|
70
|
+
- [ ] Persistent Store (redis,SQL...)
|
|
71
|
+
- [ ] CLI
|
|
72
|
+
- [ ] Render optimisation
|
|
73
|
+
|
|
74
|
+
Documentation
|
|
75
|
+
|
|
76
|
+
Full documentation is available in the [Wiki](https://github.com/Az107/TeaLeaf/wiki/Welcome-to-the-TeaLeaf!)
|
|
77
|
+
|
|
78
|
+
## Status
|
|
79
|
+
|
|
80
|
+
TeaLeaf is currently in alpha.
|
|
81
|
+
Itβs stable enough for experimentation and small demos,
|
|
82
|
+
but the public API might still change before beta.
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT License Β© 2025 β TeaLeaf Framework Made with π and Python.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "HTeaLeaf"
|
|
3
|
+
version = "0.3.0"
|
|
4
|
+
description = "Declarative SSR Framework"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"gunicorn>=23.0.0",
|
|
9
|
+
"uvicorn>=0.38.0",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["setuptools"]
|
|
14
|
+
build-backend = "setuptools.build_meta"
|
|
15
|
+
|
|
16
|
+
[tool.setuptools.package-data]
|
|
17
|
+
"TeaLeaf.Server" = ['*.js']
|
htealeaf-0.3.0/setup.cfg
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
3
|
+
from typing import Any, List, Union
|
|
4
|
+
|
|
5
|
+
from ..Magic.jslib.JSCode import JSCode
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Component:
|
|
9
|
+
"""
|
|
10
|
+
Represents an HTML component with attributes, children, and optional inline styles.
|
|
11
|
+
This class allows constructing HTML elements programmatically and managing CSS styles.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, name, *childs: Union[str, List[Any], "Component", "JSCode"]) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Initializes a new Component instance.
|
|
17
|
+
|
|
18
|
+
:param name: The tag name of the HTML element.
|
|
19
|
+
:param childs: Optional children elements, which can be strings, lists, or other Component instances.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
self.styles: str | None = None
|
|
23
|
+
self.name = name
|
|
24
|
+
self.children: list[Component | str | list | JSCode] = list(childs)
|
|
25
|
+
self.attributes: dict[str, str | None] = dict()
|
|
26
|
+
self._id: str = self._generate_id()
|
|
27
|
+
|
|
28
|
+
def _generate_id(self) -> str:
|
|
29
|
+
"""Genera un ID determinista basado en el contenido del componente."""
|
|
30
|
+
content = {
|
|
31
|
+
"name": self.name,
|
|
32
|
+
"children": [
|
|
33
|
+
child._id if isinstance(child, Component) else str(child)
|
|
34
|
+
for child in self.children
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
raw = json.dumps(content, sort_keys=True)
|
|
38
|
+
hash_str = hashlib.md5(raw.encode()).hexdigest()[:12]
|
|
39
|
+
return f"tl-{hash_str}"
|
|
40
|
+
|
|
41
|
+
def id(self, id: str):
|
|
42
|
+
"""
|
|
43
|
+
Sets the ID of the component and adds it as an attribute.
|
|
44
|
+
|
|
45
|
+
:param id: The ID to assign.
|
|
46
|
+
:return: The component instance (for method chaining).
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
self._id = id
|
|
50
|
+
return self.attr(id=id)
|
|
51
|
+
|
|
52
|
+
def classes(self, classes):
|
|
53
|
+
"""
|
|
54
|
+
Adds a CSS class attribute to the component.
|
|
55
|
+
|
|
56
|
+
:param classes: CSS class names (space-separated).
|
|
57
|
+
:return: The component instance (for method chaining).
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
self.attributes["class"] = classes
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def style(self, path: str | None = None, **attr):
|
|
64
|
+
"""
|
|
65
|
+
Adds inline styles to the component.
|
|
66
|
+
|
|
67
|
+
:param path: Optional path to an external CSS file.
|
|
68
|
+
:param attr: CSS properties to apply (e.g., color="red", margin="10px").
|
|
69
|
+
:return: The component instance (for method chaining).
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
self.styles = (self.styles or "") + f"#{self._id} {{\n"
|
|
74
|
+
self.styles += "\n".join(
|
|
75
|
+
f" {k.replace('_', '-')}: {v};" for k, v in attr.items()
|
|
76
|
+
)
|
|
77
|
+
self.styles += "\n}\n"
|
|
78
|
+
|
|
79
|
+
if path:
|
|
80
|
+
with open(path, "r") as f:
|
|
81
|
+
self.styles += f.read()
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
def attr(self,*args, **attr):
|
|
85
|
+
"""
|
|
86
|
+
Adds custom attributes to the component.
|
|
87
|
+
|
|
88
|
+
:param attr: Dictionary of attribute names and values.
|
|
89
|
+
:return: The component instance (for method chaining).
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
for arg in args:
|
|
93
|
+
self.attributes[arg] = None
|
|
94
|
+
|
|
95
|
+
for k in attr:
|
|
96
|
+
# if type(attr[k]) is str:
|
|
97
|
+
value = attr[k]
|
|
98
|
+
# if isinstance(value, JSCode):
|
|
99
|
+
# value = f"{{{{{str(value)}}}}}"
|
|
100
|
+
self.attributes[k] = value
|
|
101
|
+
# elif type(attr[k]) is FunctionType:
|
|
102
|
+
# py_f = inspect.getsource(attr[k])
|
|
103
|
+
# self.attributes[k] = f"""() => pyodide.runPython(`{py_f}`)"""
|
|
104
|
+
|
|
105
|
+
return self
|
|
106
|
+
|
|
107
|
+
def append(self, child: Union[str, "Component", list]):
|
|
108
|
+
"""
|
|
109
|
+
Appends a child element to the component.
|
|
110
|
+
|
|
111
|
+
:param child: A Component, string, or list of elements.
|
|
112
|
+
:return: The component instance (for method chaining).
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
self.children.append(child)
|
|
116
|
+
return self
|
|
117
|
+
|
|
118
|
+
def prepend(self, child: Union[str, "Component", list]):
|
|
119
|
+
"""
|
|
120
|
+
Prepends a child element to the component.
|
|
121
|
+
|
|
122
|
+
:param child: A Component, string, or list of elements.
|
|
123
|
+
:return: The component instance (for method chaining).
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
self.children.insert(0, child)
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def __build_attr__(self) -> str:
|
|
130
|
+
return " " + " ".join(
|
|
131
|
+
f"{k}='{v}'" if v is not None else f"{k}" for k, v in self.attributes.items()
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def __build_child__(self, children: list):
|
|
135
|
+
html_parts = []
|
|
136
|
+
css_parts = []
|
|
137
|
+
for child in children:
|
|
138
|
+
if isinstance(child, str):
|
|
139
|
+
html_parts.append(f"{child}")
|
|
140
|
+
elif isinstance(child, list):
|
|
141
|
+
html, css = self.__build_child__(child)
|
|
142
|
+
html_parts.append(html)
|
|
143
|
+
css_parts.append(css)
|
|
144
|
+
elif isinstance(child, Component):
|
|
145
|
+
html, css = child.build()
|
|
146
|
+
html_parts.append(html)
|
|
147
|
+
css_parts.append(css)
|
|
148
|
+
# elif isinstance(child, JSDO):
|
|
149
|
+
# print(f"JSCode: {child().raw}")
|
|
150
|
+
# html_parts.append(f"{{{{{child().raw}}}}}")
|
|
151
|
+
elif isinstance(child, JSCode):
|
|
152
|
+
# JSCode outside of an attribute should be a special tag {{jscode_name}}
|
|
153
|
+
print(f"JSCode: {child.raw}")
|
|
154
|
+
html_parts.append(f"{{{{{child.raw}}}}}")
|
|
155
|
+
else:
|
|
156
|
+
try:
|
|
157
|
+
html_parts.append(str(child))
|
|
158
|
+
except Exception:
|
|
159
|
+
continue
|
|
160
|
+
return "".join(html_parts), "".join(filter(None, css_parts))
|
|
161
|
+
|
|
162
|
+
def build(self) -> tuple[str, str]:
|
|
163
|
+
"""
|
|
164
|
+
Builds the component's HTML and CSS separately.
|
|
165
|
+
|
|
166
|
+
:return: A tuple (HTML string, CSS string)
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
if self.styles is not None and "id" not in self.attributes:
|
|
170
|
+
self.attr(id=self._id)
|
|
171
|
+
if len(self.children) == 0:
|
|
172
|
+
result = f"<{self.name}{self.__build_attr__()}/>\n"
|
|
173
|
+
else:
|
|
174
|
+
endln = "\n" if len(self.children) > 1 else ""
|
|
175
|
+
result = f"<{self.name}{self.__build_attr__()}>{endln}"
|
|
176
|
+
html, styles = self.__build_child__(self.children)
|
|
177
|
+
result += html
|
|
178
|
+
if self.styles is None:
|
|
179
|
+
self.styles = styles
|
|
180
|
+
else:
|
|
181
|
+
self.styles += styles
|
|
182
|
+
result += f"\t</{self.name}>\n"
|
|
183
|
+
css: str = "" if self.styles is None else self.styles
|
|
184
|
+
return result, css
|
|
185
|
+
|
|
186
|
+
def render(self) -> str:
|
|
187
|
+
"""
|
|
188
|
+
Builds and returns the full HTML including inline CSS inside a <style> tag.
|
|
189
|
+
|
|
190
|
+
:return: A complete HTML string with embedded CSS.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
if len(self.children) == 0:
|
|
194
|
+
result = f"<{self.name}{self.__build_attr__()}/>\n"
|
|
195
|
+
else:
|
|
196
|
+
inner_result, css = self.__build_child__(self.children)
|
|
197
|
+
if self.styles is None:
|
|
198
|
+
self.styles = css
|
|
199
|
+
else:
|
|
200
|
+
self.styles += css
|
|
201
|
+
self.styles += "\n"
|
|
202
|
+
result = f"<{self.name}{self.__build_attr__()}>\n"
|
|
203
|
+
if self.styles is not None:
|
|
204
|
+
result += f"<style>{self.styles}</style>\n"
|
|
205
|
+
result += inner_result
|
|
206
|
+
result += f"</{self.name}>\n"
|
|
207
|
+
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class ComponentMeta(type):
|
|
212
|
+
def __new__(cls, name, bases, dct):
|
|
213
|
+
if name not in ("Component", "ComponentMeta"):
|
|
214
|
+
|
|
215
|
+
def init(self, *childs):
|
|
216
|
+
super(self.__class__, self).__init__(name, *childs)
|
|
217
|
+
|
|
218
|
+
dct["__init__"] = init
|
|
219
|
+
return super().__new__(cls, name, bases, dct)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from types import FunctionType
|
|
2
|
+
from typing import Any, List, Union
|
|
3
|
+
|
|
4
|
+
from HTeaLeaf.Magic.jslib import JSCode, JSFunction, js
|
|
5
|
+
|
|
6
|
+
from .Component import Component, ComponentMeta
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class html(Component, metaclass=ComponentMeta):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
class head(Component, metaclass=ComponentMeta):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
class header(Component, metaclass=ComponentMeta):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
class link(Component, metaclass=ComponentMeta):
|
|
19
|
+
def __init__(self, *childs):
|
|
20
|
+
super().__init__("link", *childs)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class script(Component):
|
|
24
|
+
def __init__(self, *childs: Union[str, List[Any], "Component", JSFunction] ,src=None):
|
|
25
|
+
parsed_childs: Union[str, List[Any], "Component"] = []
|
|
26
|
+
for child in childs:
|
|
27
|
+
if type(child) is FunctionType:
|
|
28
|
+
parsed_childs.append(js(child))
|
|
29
|
+
elif type(child) is JSFunction:
|
|
30
|
+
parsed_childs.append(child)
|
|
31
|
+
else:
|
|
32
|
+
parsed_childs.append(child)
|
|
33
|
+
super().__init__("script", *parsed_childs)
|
|
34
|
+
self.unsafe = True
|
|
35
|
+
if src is not None:
|
|
36
|
+
self.attr(src=src)
|
|
37
|
+
self.children = [""]
|
|
38
|
+
# else:
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class style(Component, metaclass=ComponentMeta):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class body(Component, metaclass=ComponentMeta):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class h1(Component, metaclass=ComponentMeta):
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class h2(Component, metaclass=ComponentMeta):
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class h3(Component, metaclass=ComponentMeta):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class div(Component, metaclass=ComponentMeta):
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def row(self):
|
|
66
|
+
self.attr(style="display: flex; flex-direction: row")
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
def column(self):
|
|
70
|
+
self.attr(style="display: flex; flex-direction: column")
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class button(Component, metaclass=ComponentMeta):
|
|
75
|
+
|
|
76
|
+
def reactive(self,path,component_id):
|
|
77
|
+
"""
|
|
78
|
+
Makes the button reactive by linking it to a FetchComponent.
|
|
79
|
+
|
|
80
|
+
:param path: The URL to fetch new data from when clicked.
|
|
81
|
+
:param component: The FetchComponent to be updated.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
js = f"""fetchAndUpdate('{path}','{{}}','{component_id}')"""
|
|
85
|
+
self.attr(onclick=js)
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class label(Component, metaclass=ComponentMeta):
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
class checkbox(Component):
|
|
94
|
+
def __init__(self,checked = False, *childs):
|
|
95
|
+
super().__init__("input", *childs)
|
|
96
|
+
self.attr(type="checkbox")
|
|
97
|
+
if checked:
|
|
98
|
+
self.attr(checked="True")
|
|
99
|
+
|
|
100
|
+
class textInput(Component):
|
|
101
|
+
def __init__(self, *childs):
|
|
102
|
+
super().__init__("input", *childs)
|
|
103
|
+
|
|
104
|
+
class select(Component):
|
|
105
|
+
def __init__(self, items: List[str]):
|
|
106
|
+
super().__init__("select")
|
|
107
|
+
for item in items:
|
|
108
|
+
self.append(option(item))
|
|
109
|
+
|
|
110
|
+
class option(Component):
|
|
111
|
+
def __init__(self, value):
|
|
112
|
+
super().__init__("option", value)
|
|
113
|
+
self.attr(value=value)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class submit(Component):
|
|
117
|
+
def __init__(self, *childs):
|
|
118
|
+
super().__init__("input", *childs)
|
|
119
|
+
self.attr(type="submit")
|
|
120
|
+
|
|
121
|
+
class form(Component):
|
|
122
|
+
def __init__(self, *childs):
|
|
123
|
+
super().__init__("form", *childs)
|
|
124
|
+
|
|
125
|
+
def action(self, action):
|
|
126
|
+
self.attr(action=action)
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def method(self, method):
|
|
130
|
+
self.attr(method=method)
|
|
131
|
+
return self
|
|
132
|
+
|
|
133
|
+
def tl_if(condition: JSCode | str | bool, *childs):
|
|
134
|
+
if isinstance(condition, str):
|
|
135
|
+
condition = JSCode(condition)
|
|
136
|
+
|
|
137
|
+
return div(*childs).attr(style=f"display: {condition};")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from ..Html.Component import Component
|
|
2
|
+
from ..Html.Elements import script
|
|
3
|
+
from ..Server.Server import Server, ServerEvent
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def enable_reactivity(server: Server):
|
|
7
|
+
helper_script = script(src="_engine/helper.js")
|
|
8
|
+
|
|
9
|
+
def event_handler(res_code, res_body, res_headers):
|
|
10
|
+
if isinstance(res_body, Component):
|
|
11
|
+
res_body.prepend(helper_script)
|
|
12
|
+
|
|
13
|
+
server.registry_hook(ServerEvent.on_response, event_handler)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import inspect
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from HTeaLeaf.Html.Elements import script
|
|
6
|
+
|
|
7
|
+
from ..Magic.jslib.JSCode import JSCode
|
|
8
|
+
from ..Magic.jslib.JSDO import JSDO
|
|
9
|
+
|
|
10
|
+
# class localState():
|
|
11
|
+
# def __init__(self, init_state):
|
|
12
|
+
# self.do = JSDO("LocalState",init_state)
|
|
13
|
+
|
|
14
|
+
# def
|
|
15
|
+
|
|
16
|
+
# def js(self):
|
|
17
|
+
# return self.do.js()
|
|
18
|
+
|
|
19
|
+
# def get(self):
|
|
20
|
+
# return self.do.get()
|
|
21
|
+
|
|
22
|
+
# def set(self, data):
|
|
23
|
+
# return self.do.set(data)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def use_state(init_state):
|
|
28
|
+
frame = inspect.stack()[1]
|
|
29
|
+
site = f"{frame.filename}:{frame.lineno}"
|
|
30
|
+
raw = f"{site}:{json.dumps(init_state, sort_keys=True)}"
|
|
31
|
+
id = hashlib.md5(raw.encode()).hexdigest()[:12]
|
|
32
|
+
name = f"localstate_{id}"
|
|
33
|
+
|
|
34
|
+
def new():
|
|
35
|
+
return script(f"const {name} = new LocalState({json.dumps(init_state, sort_keys=True)},\"{name}\");")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
return new, JSCode(name)
|