Skip to content

Commit 278790d

Browse files
committed
functional template tag
1 parent 459d3bc commit 278790d

File tree

3 files changed

+54
-13
lines changed

3 files changed

+54
-13
lines changed

src/reactpy/backend/utils.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,10 @@ def import_dotted_path(dotted_path: str) -> Any:
2626

2727
def import_components(dotted_paths: Iterable[str]) -> dict[str, Any]:
2828
"""Imports a list of dotted paths and returns the callables."""
29-
results = {
29+
return {
3030
dotted_path: import_dotted_path(dotted_path) for dotted_path in dotted_paths
3131
}
3232

33-
# Check that all imports are components
34-
for dotted_path, component in results.items():
35-
errors: list[str] = []
36-
if not isinstance(component, ComponentType):
37-
errors.append(
38-
f"Expected ComponentType, got {type(component)} for {dotted_path}"
39-
)
40-
if errors:
41-
raise RuntimeError(". ".join(errors))
42-
43-
return results
44-
4533

4634
def check_path(url_path: str) -> str:
4735
"""Check that a path is valid URL path."""

src/reactpy/templatetag/__init__.py

Whitespace-only changes.

src/reactpy/templatetag/jinja.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import urllib.parse
2+
from typing import ClassVar
3+
from uuid import uuid4
4+
5+
from jinja2_simple_tags import StandaloneTag
6+
7+
8+
class ComponentTag(StandaloneTag):
9+
"""This allows enables a `component` tag to be used in any Jinja2 rendering context."""
10+
11+
safe_output = True
12+
tags: ClassVar[set[str]] = {"reactpy_component"}
13+
14+
def render(self, dotted_path: str, *args, **kwargs):
15+
uuid = uuid4().hex
16+
class_ = kwargs.pop("class", "")
17+
kwargs.pop("key", "") # `key` is effectively useless for the root node
18+
19+
# TODO: Fetch these from ReactPy settings
20+
client_js_path = "/reactpy/static/index.js"
21+
path_prefix = "/reactpy/"
22+
reconnect_interval = 750
23+
reconnect_max_interval = 60000
24+
reconnect_max_retries = 150
25+
reconnect_backoff_multiplier = 1.25
26+
27+
# Generate the websocket URL
28+
# TODO: This will require rewriting the websocket URL to `reactpy-ws/<uuid>?<kwargs>`
29+
component_path = f"{dotted_path}/"
30+
if kwargs.get("args") is not None:
31+
raise ValueError("Cannot specify `args` as a keyword argument")
32+
if args:
33+
kwargs["args"] = args
34+
if kwargs:
35+
component_path += f"?{urllib.parse.urlencode(kwargs)}"
36+
37+
# TODO: Turn this into a util function and maybe use it for the standalone version too?
38+
return (
39+
f'<div id="{uuid}" class="{class_}"></div>'
40+
'<script type="module" crossorigin="anonymous">'
41+
f'import {{ mountReactPy }} from "{client_js_path}";'
42+
f'const mountElement = document.getElementById("{uuid}");'
43+
"mountReactPy({"
44+
f' mountElement: document.getElementById("{uuid}"),'
45+
f' pathPrefix: "{path_prefix}",'
46+
f' appendComponentPath: "{component_path}",'
47+
f" reconnectInterval: {reconnect_interval},"
48+
f" reconnectMaxInterval: {reconnect_max_interval},"
49+
f" reconnectMaxRetries: {reconnect_max_retries},"
50+
f" reconnectBackoffMultiplier: {reconnect_backoff_multiplier},"
51+
"});"
52+
"</script>"
53+
)

0 commit comments

Comments
 (0)