diff --git a/pynecone/compiler/templates.py b/pynecone/compiler/templates.py index c16b42e07..d509a1f4d 100644 --- a/pynecone/compiler/templates.py +++ b/pynecone/compiler/templates.py @@ -56,9 +56,7 @@ def format_import(lib: str, default: str = "", rest: Optional[Set[str]] = None) DOCUMENT_ROOT = join( [ "{imports}", - "", "export default function Document() {{", - "", "return (", "{document}", ")", @@ -74,17 +72,11 @@ COMPONENT = join( [ "{imports}", "{custom_code}", - "", "{constants}", - "", "export default function Component() {{", - "", "{state}", - "", "{events}", - "", "{effects}", - "", "return (", "{render}", ")", @@ -155,22 +147,28 @@ def format_event_declaration(fn: Callable) -> str: # Effects. +ROUTER = constants.ROUTER +RESULT = constants.RESULT +PROCESSING = constants.PROCESSING +STATE = constants.STATE +EVENTS = constants.EVENTS +SET_RESULT = format_state_setter(RESULT) USE_EFFECT = join( [ "useEffect(() => {{", " const update = async () => {{", - " if (result.state != null) {{", - " setState({{", - " ...result.state,", - " events: [...state.events, ...result.events],", + f" if ({RESULT}.{STATE} != null) {{{{", + f" {{set_state}}({{{{", + f" ...{RESULT}.{STATE},", + f" events: [...{{state}}.{EVENTS}, ...{RESULT}.{EVENTS}],", " }})", - " setResult({{", - " ...result,", - " state: null,", - " processing: false,", + f" {SET_RESULT}({{{{", + f" ...{RESULT},", + f" {{state}}: null,", + f" {PROCESSING}: false,", " }})", " }}", - f" await updateState({{state}}, {{result}}, {{set_result}}, {EVENT_ENDPOINT}, {constants.ROUTER})", + f" await updateState({{state}}, {RESULT}, {SET_RESULT}, {EVENT_ENDPOINT}, {ROUTER})", " }}", " update()", "}})", diff --git a/pynecone/compiler/utils.py b/pynecone/compiler/utils.py index ff267b204..4c506e32e 100644 --- a/pynecone/compiler/utils.py +++ b/pynecone/compiler/utils.py @@ -115,9 +115,9 @@ def compile_state(state: Type[State]) -> str: state=state.get_name(), initial_state=json.dumps(initial_state) ) initial_result = { - "state": None, - "events": [], - "processing": False, + constants.STATE: None, + constants.EVENTS: [], + constants.PROCESSING: False, } result = templates.format_state( state="result", @@ -151,11 +151,8 @@ def compile_effects(state: Type[State]) -> str: A string of the compiled effects for the component. """ state_name = state.get_name() - result_name = "result" - set_result = templates.format_state_setter(result_name) - return templates.USE_EFFECT( - state=state_name, result=result_name, set_result=set_result - ) + set_state = templates.format_state_setter(state_name) + return templates.USE_EFFECT(state=state_name, set_state=set_state) def compile_render(component: Component) -> str: diff --git a/pynecone/components/tags/tag.py b/pynecone/components/tags/tag.py index 26c70993f..1d4d4c47d 100644 --- a/pynecone/components/tags/tag.py +++ b/pynecone/components/tags/tag.py @@ -5,7 +5,7 @@ from __future__ import annotations import json import os import re -from typing import Any, Dict, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from plotly.graph_objects import Figure from plotly.io import to_json @@ -15,6 +15,9 @@ from pynecone.base import Base from pynecone.event import EventChain from pynecone.var import Var +if TYPE_CHECKING: + from pynecone.components.component import ComponentStyle + class Tag(Base): """A React tag.""" @@ -43,87 +46,86 @@ class Tag(Base): super().__init__(*args, **kwargs) @staticmethod - def format_attr_value( - value: Union[Var, EventChain, Dict[str, Var], str], + def format_prop( + prop: Union[Var, EventChain, ComponentStyle, str], ) -> Union[int, float, str]: - """Format an attribute value. + """Format a prop. Args: - value: The value of the attribute + prop: The prop to format. Returns: - The formatted value to display within the tag. + The formatted prop to display within a tag. """ def format_fn(value): args = ",".join([":".join((name, val)) for name, val in value.args]) return f"E(\"{utils.to_snake_case(value.handler.fn.__qualname__)}\", {utils.wrap(args, '{')})" - # Handle var attributes. - if isinstance(value, Var): - if not value.is_local or value.is_string: - return str(value) - if issubclass(value.type_, str): - value = json.dumps(value.full_name) - value = re.sub('"{', "", value) - value = re.sub('}"', "", value) - value = re.sub('"`', '{"', value) - value = re.sub('`"', '"}', value) - return value - value = value.full_name + # Handle var props. + if isinstance(prop, Var): + if not prop.is_local or prop.is_string: + return str(prop) + if issubclass(prop.type_, str): + prop = json.dumps(prop.full_name) + prop = re.sub('"{', "", prop) + prop = re.sub('}"', "", prop) + prop = re.sub('"`', '{"', prop) + prop = re.sub('`"', '"}', prop) + return prop + prop = prop.full_name # Handle events. - elif isinstance(value, EventChain): - local_args = ",".join(value.events[0].local_args) - fns = ",".join([format_fn(event) for event in value.events]) - value = f"({local_args}) => Event([{fns}])" + elif isinstance(prop, EventChain): + local_args = ",".join(prop.events[0].local_args) + fns = ",".join([format_fn(event) for event in prop.events]) + prop = f"({local_args}) => Event([{fns}])" # Handle other types. - elif isinstance(value, str): - if utils.is_wrapped(value, "{"): - return value - return json.dumps(value) + elif isinstance(prop, str): + if utils.is_wrapped(prop, "{"): + return prop + return json.dumps(prop) - elif isinstance(value, Figure): - value = json.loads(to_json(value))["data"] + elif isinstance(prop, Figure): + prop = json.loads(to_json(prop))["data"] # For dictionaries, convert any properties to strings. else: - if isinstance(value, dict): - value = json.dumps( + if isinstance(prop, dict): + prop = json.dumps( { key: str(val) if isinstance(val, Var) else val - for key, val in value.items() + for key, val in prop.items() } ) else: - value = json.dumps(value) + prop = json.dumps(prop) - value = re.sub('"{', "", value) - value = re.sub('}"', "", value) - value = re.sub('"`', '{"', value) - value = re.sub('`"', '"}', value) + prop = re.sub('"{', "", prop) + prop = re.sub('}"', "", prop) + prop = re.sub('"`', '{"', prop) + prop = re.sub('`"', '"}', prop) # Wrap the variable in braces. - assert isinstance(value, str), "The value must be a string." - return utils.wrap(value, "{", check_first=False) + assert isinstance(prop, str), "The prop must be a string." + return utils.wrap(prop, "{", check_first=False) def format_props(self) -> str: - """Format a dictionary of attributes. + """Format the tag's props. Returns: - The formatted attributes. + The formatted props. """ - # If there are no attributes, return an empty string. + # If there are no props, return an empty string. if len(self.props) == 0: return "" - # Get the string representation of all the attributes joined. - # We need a space at the beginning for formatting. + # Format all the props. return os.linesep.join( - f"{name}={self.format_attr_value(value)}" - for name, value in self.props.items() - if value is not None + f"{name}={self.format_prop(prop)}" + for name, prop in self.props.items() + if prop is not None ) def __str__(self) -> str: @@ -132,7 +134,7 @@ class Tag(Base): Returns: The React code to render the tag. """ - # Get the tag attributes. + # Get the tag props. props_str = self.format_props() if len(props_str) > 0: props_str = " " + props_str @@ -149,33 +151,33 @@ class Tag(Base): return tag_str def add_props(self, **kwargs: Optional[Any]) -> Tag: - """Add attributes to the tag. + """Add props to the tag. Args: - **kwargs: The attributes to add. + **kwargs: The props to add. Returns: - The tag with the attributes added. + The tag with the props added. """ self.props.update( { - utils.to_camel_case(name): attr - if utils._isinstance(attr, Union[EventChain, dict]) - else Var.create(attr) - for name, attr in kwargs.items() - if self.is_valid_attr(attr) + utils.to_camel_case(name): prop + if utils._isinstance(prop, Union[EventChain, dict]) + else Var.create(prop) + for name, prop in kwargs.items() + if self.is_valid_prop(prop) } ) return self def remove_props(self, *args: str) -> Tag: - """Remove attributes from the tag. + """Remove props from the tag. Args: - *args: The attributes to remove. + *args: The props to remove. Returns: - The tag with the attributes removed. + The tag with the props removed. """ for name in args: if name in self.props: @@ -183,13 +185,13 @@ class Tag(Base): return self @staticmethod - def is_valid_attr(attr: Optional[Var]) -> bool: - """Check if the attr is valid. + def is_valid_prop(prop: Optional[Var]) -> bool: + """Check if the prop is valid. Args: - attr: The value to check. + prop: The prop to check. Returns: - Whether the value is valid. + Whether the prop is valid. """ - return attr is not None and not (isinstance(attr, dict) and len(attr) == 0) + return prop is not None and not (isinstance(prop, dict) and len(prop) == 0) diff --git a/pynecone/constants.py b/pynecone/constants.py index 7348ae4b5..89325e602 100644 --- a/pynecone/constants.py +++ b/pynecone/constants.py @@ -70,6 +70,14 @@ APP_VAR = "app" API_VAR = "api" # The name of the router variable. ROUTER = "router" +# The name of the variable to hold API results. +RESULT = "result" +# The name of the process variable. +PROCESSING = "processing" +# The name of the state variable. +STATE = "state" +# The name of the events variable. +EVENTS = "events" # The name of the initial hydrate event. HYDRATE = "hydrate" # The name of the index page.