component as literal vars

This commit is contained in:
Khaleel Al-Adhami 2024-10-22 18:58:33 -07:00
parent 3eab2b6e7d
commit 42b8586ab9
5 changed files with 210 additions and 39 deletions

View File

@ -36,14 +36,10 @@
{# component: component dictionary #}
{% macro render_tag(component) %}
<{{component.name}} {{- render_props(component.props) }}>
{%- if component.args is not none -%}
{{- render_arg_content(component) }}
{%- else -%}
{{ component.contents }}
{% for child in component.children %}
{{ render(child) }}
{% endfor %}
{%- endif -%}
{{ component.contents }}
{% for child in component.children %}
{{ render(child) }}
{% endfor %}
</{{component.name}}>
{%- endmacro %}

View File

@ -7,7 +7,7 @@ from typing import Any, Iterator
from reflex.components.component import Component
from reflex.components.tags import Tag
from reflex.components.tags.tagless import Tagless
from reflex.vars import ArrayVar, BooleanVar, ObjectVar, Var
from reflex.vars import BooleanVar, ObjectVar, Var
class Bare(Component):
@ -33,7 +33,7 @@ class Bare(Component):
def _render(self) -> Tag:
if isinstance(self.contents, Var):
if isinstance(self.contents, (BooleanVar, ObjectVar, ArrayVar)):
if isinstance(self.contents, (BooleanVar, ObjectVar)):
return Tagless(contents=f"{{{str(self.contents.to_string())}}}")
return Tagless(contents=f"{{{str(self.contents)}}}")
return Tagless(contents=str(self.contents))

View File

@ -3,6 +3,7 @@
from __future__ import annotations
import copy
import dataclasses
import typing
from abc import ABC, abstractmethod
from functools import lru_cache, wraps
@ -58,7 +59,14 @@ from reflex.utils.imports import (
parse_imports,
)
from reflex.vars import VarData
from reflex.vars.base import LiteralVar, Var
from reflex.vars.base import (
CachedVarOperation,
LiteralVar,
Var,
cached_property_no_lock,
)
from reflex.vars.function import FunctionStringVar
from reflex.vars.object import ObjectVar
from reflex.vars.sequence import LiteralArrayVar
@ -2340,3 +2348,119 @@ class MemoizationLeaf(Component):
load_dynamic_serializer()
class ComponentVar(Var[Component], python_types=Component):
"""A Var that represents a Component."""
def empty_component() -> Component:
"""Create an empty component.
Returns:
An empty component.
"""
from reflex.components.base.bare import Bare
return Bare.create("")
@dataclasses.dataclass(
eq=False,
frozen=True,
)
class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar):
"""A Var that represents a Component."""
_var_value: Component = dataclasses.field(default_factory=empty_component)
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""Get the name of the var.
Returns:
The name of the var.
"""
tag = self._var_value._render()
props = Var.create(tag.props).to(ObjectVar)
for prop in tag.special_props:
props = props.merge(prop)
contents = getattr(self._var_value, "contents", None)
tag_name = Var(tag.name) if tag.name else Var("Fragment")
return str(
FunctionStringVar.create(
"jsx",
).call(
tag_name,
props,
*([Var.create(contents)] if contents is not None else []),
*[Var.create(child) for child in self._var_value.children],
)
)
@cached_property_no_lock
def _cached_get_all_var_data(self) -> VarData | None:
"""Get the VarData for the var.
Returns:
The VarData for the var.
"""
return VarData.merge(
VarData(
imports={
"@emotion/react": [
ImportVar(tag="jsx"),
],
}
),
VarData(
imports=self._var_value._get_all_imports(collapse=True),
),
*(
[
VarData(
imports={
"react": [
ImportVar(tag="Fragment"),
],
}
)
]
if not self._var_value.tag
else []
),
)
def __hash__(self) -> int:
"""Get the hash of the var.
Returns:
The hash of the var.
"""
return hash((self.__class__.__name__,))
@classmethod
def create(
cls,
value: Component,
_var_data: VarData | None = None,
):
"""Create a var from a value.
Args:
value: The value of the var.
_var_data: Additional hooks and imports associated with the Var.
Returns:
The var.
"""
return LiteralComponentVar(
_js_expr="",
_var_type=type(value),
_var_data=_var_data,
_var_value=value,
)

View File

@ -5,11 +5,17 @@ from __future__ import annotations
from pathlib import Path
from typing import Any, Callable, ClassVar, Dict, List, Optional, Tuple
from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
from reflex.components.component import (
Component,
ComponentNamespace,
MemoizationLeaf,
StatefulComponent,
)
from reflex.components.el.elements.forms import Input
from reflex.components.radix.themes.layout.box import Box
from reflex.config import environment
from reflex.constants import Dirs
from reflex.constants.compiler import Imports
from reflex.event import (
CallableEventSpec,
EventChain,
@ -19,9 +25,10 @@ from reflex.event import (
call_script,
parse_args_spec,
)
from reflex.utils import format
from reflex.utils.imports import ImportVar
from reflex.vars import VarData
from reflex.vars.base import CallableVar, LiteralVar, Var
from reflex.vars.base import CallableVar, LiteralVar, Var, get_unique_variable_name
from reflex.vars.sequence import LiteralStringVar
DEFAULT_UPLOAD_ID: str = "default"
@ -179,9 +186,7 @@ class Upload(MemoizationLeaf):
library = "react-dropzone@14.2.10"
tag = "ReactDropzone"
is_default = True
tag = ""
# The list of accepted file types. This should be a dictionary of MIME types as keys and array of file formats as
# values.
@ -201,7 +206,7 @@ class Upload(MemoizationLeaf):
min_size: Var[int]
# Whether to allow multiple files to be uploaded.
multiple: Var[bool] = True # type: ignore
multiple: Var[bool]
# Whether to disable click to upload.
no_click: Var[bool]
@ -219,11 +224,12 @@ class Upload(MemoizationLeaf):
on_drop: EventHandler[_on_drop_spec]
@classmethod
def create(cls, *children, **props) -> Component:
def create(cls, *children, multiple=True, **props) -> Component:
"""Create an upload component.
Args:
*children: The children of the component.
multiple: Whether to allow multiple files to be uploaded.
**props: The properties of the component.
Returns:
@ -232,6 +238,8 @@ class Upload(MemoizationLeaf):
# Mark the Upload component as used in the app.
cls.is_used = True
props["multiple"] = multiple
# Apply the default classname
given_class_name = props.pop("class_name", [])
if isinstance(given_class_name, str):
@ -243,17 +251,6 @@ class Upload(MemoizationLeaf):
upload_props = {
key: value for key, value in props.items() if key in supported_props
}
# The file input to use.
upload = Input.create(type="file")
upload.special_props = [Var(_js_expr="{...getInputProps()}", _var_type=None)]
# The dropzone to use.
zone = Box.create(
upload,
*children,
**{k: v for k, v in props.items() if k not in supported_props},
)
zone.special_props = [Var(_js_expr="{...getRootProps()}", _var_type=None)]
# Create the component.
upload_props["id"] = props.get("id", DEFAULT_UPLOAD_ID)
@ -275,9 +272,71 @@ class Upload(MemoizationLeaf):
),
)
upload_props["on_drop"] = on_drop
input_props_unique_name = get_unique_variable_name()
root_props_unique_name = get_unique_variable_name()
event_var, callback_str = StatefulComponent._get_memoized_event_triggers(
Box.create(on_click=upload_props["on_drop"])
)["on_click"]
upload_props["on_drop"] = event_var
upload_props = {
format.to_camel_case(key): value for key, value in upload_props.items()
}
var_data = VarData.merge(
VarData(
imports=Imports.EVENTS,
hooks={
"const [addEvents, connectError] = useContext(EventLoopContext);": None
},
),
event_var._get_all_var_data(),
VarData(
hooks={
callback_str: None,
f"const {{getRootProps: {root_props_unique_name}, getInputProps: {input_props_unique_name}}} = useDropzone({
str(Var.create({
'onDrop': event_var,
**upload_props,
}))
});": None,
},
imports={
"react-dropzone": "useDropzone",
**Imports.EVENTS,
},
),
)
# The file input to use.
upload = Input.create(type="file")
upload.special_props = [
Var(
_js_expr=f"{{...{input_props_unique_name}()}}",
_var_type=None,
_var_data=var_data,
)
]
# The dropzone to use.
zone = Box.create(
upload,
*children,
**{k: v for k, v in props.items() if k not in supported_props},
)
zone.special_props = [
Var(
_js_expr=f"{{...{root_props_unique_name}()}}",
_var_type=None,
_var_data=var_data,
)
]
return super().create(
zone,
**upload_props,
)
@classmethod
@ -295,11 +354,6 @@ class Upload(MemoizationLeaf):
return (arg_value[0], placeholder)
return arg_value
def _render(self):
out = super()._render()
out.args = ("getRootProps", "getInputProps")
return out
@staticmethod
def _get_app_wrap_components() -> dict[tuple[int, str], Component]:
return {

View File

@ -3,7 +3,7 @@
from __future__ import annotations
import dataclasses
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import Any, Dict, List, Optional, Union
from reflex.event import EventChain
from reflex.utils import format, types
@ -23,9 +23,6 @@ class Tag:
# The inner contents of the tag.
contents: str = ""
# Args to pass to the tag.
args: Optional[Tuple[str, ...]] = None
# Special props that aren't key value pairs.
special_props: List[Var] = dataclasses.field(default_factory=list)