diff --git a/reflex/__init__.py b/reflex/__init__.py index b204615c2..c2aef8a66 100644 --- a/reflex/__init__.py +++ b/reflex/__init__.py @@ -4,10 +4,13 @@ Anything imported here will be available in the default Reflex import as `rx.*`. To signal to typecheckers that something should be reexported, we use the Flask "import name as name" syntax. """ +from __future__ import annotations + import importlib from typing import Type from reflex.page import page as page +from reflex.utils import console from reflex.utils.format import to_snake_case _ALL_COMPONENTS = [ @@ -181,10 +184,6 @@ _ALL_COMPONENTS = [ "StatGroup", "StatHelpText", "StatLabel", - "StatArrow", - "StatGroup", - "StatHelpText", - "StatLabel", "StatNumber", "Step", "StepDescription", @@ -250,7 +249,7 @@ _MAPPING = { "reflex.base": ["base", "Base"], "reflex.compiler": ["compiler"], "reflex.compiler.utils": ["get_asset_path"], - "reflex.components": _ALL_COMPONENTS, + "reflex.components": _ALL_COMPONENTS + ["chakra", "next"], "reflex.components.component": ["memo"], "reflex.components.graphing": ["recharts"], "reflex.components.datadisplay.moment": ["MomentDelta"], @@ -286,7 +285,31 @@ _MAPPING = { "reflex.utils": ["utils"], "reflex.vars": ["vars", "cached_var", "Var"], } -_MAPPING = {value: key for key, values in _MAPPING.items() for value in values} + + +def _reverse_mapping(mapping: dict[str, list]) -> dict[str, str]: + """Reverse the mapping used to lazy loading, and check for conflicting name. + + Args: + mapping: The mapping to reverse. + + Returns: + The reversed mapping. + """ + reversed_mapping = {} + for key, values in mapping.items(): + for value in values: + if value not in reversed_mapping: + reversed_mapping[value] = key + else: + console.warn( + f"Key {value} is present multiple times in the imports _MAPPING: {key} / {reversed_mapping[value]}" + ) + return reversed_mapping + + +# _MAPPING = {value: key for key, values in _MAPPING.items() for value in values} +_MAPPING = _reverse_mapping(_MAPPING) def _removeprefix(text, prefix): diff --git a/reflex/__init__.pyi b/reflex/__init__.pyi index 25bbd74c6..48c7e18bf 100644 --- a/reflex/__init__.pyi +++ b/reflex/__init__.pyi @@ -440,6 +440,8 @@ from reflex.components import clear_selected_files as clear_selected_files from reflex.components import EditorButtonList as EditorButtonList from reflex.components import EditorOptions as EditorOptions from reflex.components import NoSSRComponent as NoSSRComponent +from reflex.components import chakra as chakra +from reflex.components import next as next from reflex.components.component import memo as memo from reflex.components.graphing import recharts as recharts from reflex.components.datadisplay.moment import MomentDelta as MomentDelta diff --git a/reflex/components/__init__.py b/reflex/components/__init__.py index af20ea55b..9426ba8fa 100644 --- a/reflex/components/__init__.py +++ b/reflex/components/__init__.py @@ -1,7 +1,9 @@ """Import all the components.""" from __future__ import annotations +from . import next as next from .base import Script +from .chakra import * from .component import Component from .component import NoSSRComponent as NoSSRComponent from .datadisplay import * diff --git a/reflex/components/chakra/__init__.py b/reflex/components/chakra/__init__.py new file mode 100644 index 000000000..490d479f7 --- /dev/null +++ b/reflex/components/chakra/__init__.py @@ -0,0 +1,23 @@ +"""Chakra components.""" + + +import importlib +from typing import Type + +from .media.image import Image + +image = Image.create + +# _MAPPING = { +# "image": "media", +# } + + +# def __getattr__(name: str) -> Type: +# print(f"importing {name}") +# if name not in _MAPPING: +# return importlib.import_module(name) + +# module = importlib.import_module(_MAPPING[name], package=".") + +# return getattr(module, name) if name != _MAPPING[name].rsplit(".")[-1] else module diff --git a/reflex/components/media/image.py b/reflex/components/chakra/media/image.py similarity index 76% rename from reflex/components/media/image.py rename to reflex/components/chakra/media/image.py index 62e71df2a..fed382d56 100644 --- a/reflex/components/media/image.py +++ b/reflex/components/chakra/media/image.py @@ -1,13 +1,10 @@ """An image component.""" from __future__ import annotations -import base64 -import io from typing import Any, Optional, Union from reflex.components.component import Component from reflex.components.libs.chakra import ChakraComponent, LiteralImageLoading -from reflex.utils.serializers import serializer from reflex.vars import Var @@ -15,7 +12,7 @@ class Image(ChakraComponent): """Display an image.""" tag = "Image" - + alias = "ChakraImage" # How to align the image within its bounds. It maps to css `object-position` property. align: Var[str] @@ -79,27 +76,3 @@ class Image(ChakraComponent): if src is not None and not isinstance(src, (Var)): props["src"] = Var.create(value=src, _var_is_string=True) return super().create(*children, **props) - - -try: - from PIL.Image import Image as Img - - @serializer - def serialize_image(image: Img) -> str: - """Serialize a plotly figure. - - Args: - image: The image to serialize. - - Returns: - The serialized image. - """ - buff = io.BytesIO() - image.save(buff, format=getattr(image, "format", None) or "PNG") - image_bytes = buff.getvalue() - base64_image = base64.b64encode(image_bytes).decode("utf-8") - mime_type = getattr(image, "get_format_mimetype", lambda: "image/png")() - return f"data:{mime_type};base64,{base64_image}" - -except ImportError: - pass diff --git a/reflex/components/chakra/media/image.pyi b/reflex/components/chakra/media/image.pyi new file mode 100644 index 000000000..a5c05d1bc --- /dev/null +++ b/reflex/components/chakra/media/image.pyi @@ -0,0 +1,120 @@ +"""Stub file for reflex/components/chakra/media/image.py""" +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `scripts/pyi_generator.py`! +# ------------------------------------------------------ + +from typing import Any, Dict, Literal, Optional, Union, overload +from reflex.vars import Var, BaseVar, ComputedVar +from reflex.event import EventChain, EventHandler, EventSpec +from reflex.style import Style +from typing import Any, Optional, Union +from reflex.components.component import Component +from reflex.components.libs.chakra import ChakraComponent, LiteralImageLoading +from reflex.vars import Var + +class Image(ChakraComponent): + def get_event_triggers(self) -> dict[str, Union[Var, Any]]: ... + @overload + @classmethod + def create( # type: ignore + cls, + *children, + align: Optional[Union[Var[str], str]] = None, + fallback: Optional[Component] = None, + fallback_src: Optional[Union[Var[str], str]] = None, + fit: Optional[Union[Var[str], str]] = None, + html_height: Optional[Union[Var[str], str]] = None, + html_width: Optional[Union[Var[str], str]] = None, + ignore_fallback: Optional[Union[Var[bool], bool]] = None, + loading: Optional[ + Union[Var[Literal["eager", "lazy"]], Literal["eager", "lazy"]] + ] = None, + src: Optional[Union[Var[Any], Any]] = None, + alt: Optional[Union[Var[str], str]] = None, + src_set: Optional[Union[Var[str], str]] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, + on_blur: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_click: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_context_menu: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_double_click: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_error: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_focus: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_load: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mount: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_down: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_enter: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_leave: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_move: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_out: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_over: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_up: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_scroll: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_unmount: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + **props + ) -> "Image": + """Create an Image component. + + Args: + *children: The children of the image. + align: How to align the image within its bounds. It maps to css `object-position` property. + fallback: Fallback Reflex component to show if image is loading or image fails. + fallback_src: Fallback image src to show if image is loading or image fails. + fit: How the image to fit within its bounds. It maps to css `object-fit` property. + html_height: The native HTML height attribute to the passed to the img. + html_width: The native HTML width attribute to the passed to the img. + ignore_fallback: If true, opt out of the fallbackSrc logic and use as img. + loading: "eager" | "lazy" + src: The path/url to the image or PIL image object. + alt: The alt text of the image. + src_set: Provide multiple sources for an image, allowing the browser to select the most appropriate source based on factors like screen resolution and device capabilities. Learn more _[here](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images)_ + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: The props of the image. + + Returns: + The Image component. + """ + ... diff --git a/reflex/components/component.py b/reflex/components/component.py index e85b6dd36..171a21ac0 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -785,6 +785,10 @@ class Component(BaseComponent, ABC): for child in self.children: dynamic_imports |= child.get_dynamic_imports() + for prop in self.get_component_props(): + if getattr(self, prop) is not None: + dynamic_imports |= getattr(self, prop).get_dynamic_imports() + # Return the dynamic imports return dynamic_imports diff --git a/reflex/components/media/__init__.py b/reflex/components/media/__init__.py index 5873a7b76..b37eccd6e 100644 --- a/reflex/components/media/__init__.py +++ b/reflex/components/media/__init__.py @@ -3,7 +3,6 @@ from .audio import Audio from .avatar import Avatar, AvatarBadge, AvatarGroup from .icon import Icon -from .image import Image from .video import Video __all__ = [f for f in dir() if f[0].isupper()] # type: ignore diff --git a/reflex/components/next/__init__.py b/reflex/components/next/__init__.py new file mode 100644 index 000000000..090d5c615 --- /dev/null +++ b/reflex/components/next/__init__.py @@ -0,0 +1,8 @@ +"""Namespace for components provided by next packages.""" + +from .base import NextComponent +from .image import Image +from .video import Video + +image = Image.create +video = Video.create diff --git a/reflex/components/next/base.py b/reflex/components/next/base.py new file mode 100644 index 000000000..f962497d3 --- /dev/null +++ b/reflex/components/next/base.py @@ -0,0 +1,8 @@ +"""Base for NextJS components.""" +from reflex.components.component import Component + + +class NextComponent(Component): + """A Component used as based for any NextJS component.""" + + ... diff --git a/reflex/components/next/base.pyi b/reflex/components/next/base.pyi new file mode 100644 index 000000000..57fab6a9a --- /dev/null +++ b/reflex/components/next/base.pyi @@ -0,0 +1,91 @@ +"""Stub file for reflex/components/next/base.py""" +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `scripts/pyi_generator.py`! +# ------------------------------------------------------ + +from typing import Any, Dict, Literal, Optional, Union, overload +from reflex.vars import Var, BaseVar, ComputedVar +from reflex.event import EventChain, EventHandler, EventSpec +from reflex.style import Style +from reflex.components.component import Component + +class NextComponent(Component): + ... + + @overload + @classmethod + def create( # type: ignore + cls, + *children, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, + on_blur: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_click: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_context_menu: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_double_click: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_focus: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mount: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_down: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_enter: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_leave: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_move: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_out: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_over: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_up: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_scroll: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_unmount: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + **props + ) -> "NextComponent": + """Create the component. + + Args: + *children: The children of the component. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: The props of the component. + + Returns: + The component. + + Raises: + TypeError: If an invalid child is passed. + """ + ... diff --git a/reflex/components/next/image.py b/reflex/components/next/image.py new file mode 100644 index 000000000..cc442c113 --- /dev/null +++ b/reflex/components/next/image.py @@ -0,0 +1,140 @@ +"""Image component from next/image.""" +import base64 +import io +from typing import Any, Dict, Literal, Optional, Union + +from reflex.utils import types +from reflex.utils.serializers import serializer +from reflex.vars import Var + +from .base import NextComponent + + +class Image(NextComponent): + """Display an image.""" + + tag = "Image" + library = "next/image" + is_default = True + + # This can be either an absolute external URL, or an internal path + src: Var[Any] + + # Represents the rendered width in pixels, so it will affect how large the image appears. + width: Var[Any] + + # Represents the rendered height in pixels, so it will affect how large the image appears. + height: Var[Any] + + # Used to describe the image for screen readers and search engines. + alt: Var[str] + + # A custom function used to resolve image URLs. + loader: Var[Any] + + # A boolean that causes the image to fill the parent element, which is useful when the width and height are unknown. Default to True + fill: Var[bool] + + # A string, similar to a media query, that provides information about how wide the image will be at different breakpoints. + sizes: Var[str] + + # The quality of the optimized image, an integer between 1 and 100, where 100 is the best quality and therefore largest file size. Defaults to 75. + quality: Var[int] + + # When true, the image will be considered high priority and preload. Lazy loading is automatically disabled for images using priority. + priority: Var[bool] + + # A placeholder to use while the image is loading. Possible values are blur, empty, or data:image/.... Defaults to empty. + placeholder: Var[str] + + # Allows passing CSS styles to the underlying image element. + # style: Var[Any] + + # The loading behavior of the image. Defaults to lazy. Can hurt performance, recommended to use `priority` instead. + loading: Var[Literal["lazy", "eager"]] + + # A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur". + blurDataURL: Var[str] + + def get_event_triggers(self) -> Dict[str, Any]: + """The event triggers of the component. + + Returns: + The dict describing the event triggers. + """ + return { + **super().get_event_triggers(), + "on_load": lambda: [], + "on_error": lambda: [], + } + + @classmethod + def create( + cls, + *children, + width: Optional[Union[int, str]] = None, + height: Optional[Union[int, str]] = None, + **props, + ): + """Create an Image component from next/image. + + Args: + *children: The children of the component. + width: The width of the image. + height: The height of the image. + **props:The props of the component. + + Returns: + _type_: _description_ + """ + style = props.get("style", {}) + DEFAULT_W_H = "100%" + + def check_prop_type(prop_name, prop_value): + if types.check_prop_in_allowed_types(prop_value, allowed_types=[int]): + props[prop_name] = prop_value + + elif types.check_prop_in_allowed_types(prop_value, allowed_types=[str]): + props[prop_name] = 0 + style[prop_name] = prop_value + else: + props[prop_name] = 0 + style[prop_name] = DEFAULT_W_H + + check_prop_type("width", width) + check_prop_type("height", height) + + props["style"] = style + + # mysteriously, following `sizes` prop is needed to avoid blury images. + props["sizes"] = "100vw" + + src = props.get("src", None) + if src is not None and not isinstance(src, (Var)): + props["src"] = Var.create(value=src, _var_is_string=True) + + return super().create(*children, **props) + + +try: + from PIL.Image import Image as Img + + @serializer + def serialize_image(image: Img) -> str: + """Serialize a plotly figure. + + Args: + image: The image to serialize. + + Returns: + The serialized image. + """ + buff = io.BytesIO() + image.save(buff, format=getattr(image, "format", None) or "PNG") + image_bytes = buff.getvalue() + base64_image = base64.b64encode(image_bytes).decode("utf-8") + mime_type = getattr(image, "get_format_mimetype", lambda: "image/png")() + return f"data:{mime_type};base64,{base64_image}" + +except ImportError: + pass diff --git a/reflex/components/next/image.pyi b/reflex/components/next/image.pyi new file mode 100644 index 000000000..2df6ba4f2 --- /dev/null +++ b/reflex/components/next/image.pyi @@ -0,0 +1,134 @@ +"""Stub file for reflex/components/next/image.py""" +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `scripts/pyi_generator.py`! +# ------------------------------------------------------ + +from typing import Any, Dict, Literal, Optional, Union, overload +from reflex.vars import Var, BaseVar, ComputedVar +from reflex.event import EventChain, EventHandler, EventSpec +from reflex.style import Style +import base64 +import io +from typing import Any, Dict, Literal, Optional, Union +from reflex.utils import types +from reflex.utils.serializers import serializer +from reflex.vars import Var +from .base import NextComponent + +class Image(NextComponent): + def get_event_triggers(self) -> Dict[str, Any]: ... + @overload + @classmethod + def create( # type: ignore + cls, + *children, + width: Optional[Union[str, int]] = None, + height: Optional[Union[str, int]] = None, + src: Optional[Union[Var[Any], Any]] = None, + alt: Optional[Union[Var[str], str]] = None, + loader: Optional[Union[Var[Any], Any]] = None, + fill: Optional[Union[Var[bool], bool]] = None, + sizes: Optional[Union[Var[str], str]] = None, + quality: Optional[Union[Var[int], int]] = None, + priority: Optional[Union[Var[bool], bool]] = None, + placeholder: Optional[Union[Var[str], str]] = None, + loading: Optional[ + Union[Var[Literal["lazy", "eager"]], Literal["lazy", "eager"]] + ] = None, + blurDataURL: Optional[Union[Var[str], str]] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, + on_blur: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_click: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_context_menu: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_double_click: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_error: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_focus: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_load: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mount: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_down: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_enter: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_leave: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_move: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_out: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_over: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_up: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_scroll: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_unmount: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + **props + ) -> "Image": + """Create an Image component from next/image. + + Args: + *children: The children of the component. + width: The width of the image. + height: The height of the image. + src: This can be either an absolute external URL, or an internal path + alt: Used to describe the image for screen readers and search engines. + loader: A custom function used to resolve image URLs. + fill: A boolean that causes the image to fill the parent element, which is useful when the width and height are unknown. Default to True + sizes: A string, similar to a media query, that provides information about how wide the image will be at different breakpoints. + quality: The quality of the optimized image, an integer between 1 and 100, where 100 is the best quality and therefore largest file size. Defaults to 75. + priority: When true, the image will be considered high priority and preload. Lazy loading is automatically disabled for images using priority. + placeholder: A placeholder to use while the image is loading. Possible values are blur, empty, or data:image/.... Defaults to empty. + loading: Allows passing CSS styles to the underlying image element. style: Var[Any] The loading behavior of the image. Defaults to lazy. Can hurt performance, recommended to use `priority` instead. + blurDataURL: A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur". + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props:The props of the component. + + Returns: + _type_: _description_ + """ + ... + +try: + from PIL.Image import Image as Img + + @serializer + def serialize_image(image: Img) -> str: ... + +except ImportError: + pass diff --git a/reflex/components/next/video.py b/reflex/components/next/video.py new file mode 100644 index 000000000..ae6007671 --- /dev/null +++ b/reflex/components/next/video.py @@ -0,0 +1,33 @@ +"""Wrapping of the next-video component.""" + +from typing import Optional + +from reflex.components.component import Component +from reflex.vars import Var + +from .base import NextComponent + + +class Video(NextComponent): + """A video component from NextJS.""" + + tag = "Video" + library = "next-video" + is_default = True + # the URL + src: Var[str] + + as_: Optional[Component] + + @classmethod + def create(cls, *children, **props) -> NextComponent: + """Create a Video component. + + Args: + *children: The children of the component. + **props: The props of the component. + + Returns: + The Video component. + """ + return super().create(*children, **props) diff --git a/reflex/components/next/video.pyi b/reflex/components/next/video.pyi new file mode 100644 index 000000000..cee5c4c9d --- /dev/null +++ b/reflex/components/next/video.pyi @@ -0,0 +1,92 @@ +"""Stub file for reflex/components/next/video.py""" +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `scripts/pyi_generator.py`! +# ------------------------------------------------------ + +from typing import Any, Dict, Literal, Optional, Union, overload +from reflex.vars import Var, BaseVar, ComputedVar +from reflex.event import EventChain, EventHandler, EventSpec +from reflex.style import Style +from typing import Optional +from reflex.components.component import Component +from reflex.vars import Var +from .base import NextComponent + +class Video(NextComponent): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + src: Optional[Union[Var[str], str]] = None, + as_: Optional[Component] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, + on_blur: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_click: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_context_menu: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_double_click: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_focus: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mount: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_down: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_enter: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_leave: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_move: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_out: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_over: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_up: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_scroll: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_unmount: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + **props + ) -> "Video": + """Create a Video component. + + Args: + *children: The children of the component. + src: the URL + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: The props of the component. + + Returns: + The Video component. + """ + ... diff --git a/reflex/utils/types.py b/reflex/utils/types.py index 806684954..ef5255e9c 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -272,6 +272,23 @@ def check_type_in_allowed_types(value_type: Type, allowed_types: Iterable) -> bo return get_base_class(value_type) in allowed_types +def check_prop_in_allowed_types(prop: Any, allowed_types: Iterable) -> bool: + """Check that a prop value is in a list of allowed types. + Does the check in a way that works regardless if it's a raw value or a state Var. + + Args: + prop: The prop to check. + allowed_types: The list of allowed types. + + Returns: + If the prop type match one of the allowed_types. + """ + from reflex.vars import Var + + type_ = prop._var_type if _isinstance(prop, Var) else type(prop) + return type_ in allowed_types + + # Store this here for performance. StateBases = get_base_class(StateVar) StateIterBases = get_base_class(StateIterVar) diff --git a/tests/components/media/test_image.py b/tests/components/media/test_image.py index 9d11dcfc0..c9755d4d7 100644 --- a/tests/components/media/test_image.py +++ b/tests/components/media/test_image.py @@ -1,63 +1,58 @@ +# PIL is only available in python 3.8+ +import numpy as np +import PIL import pytest +from PIL.Image import Image as Img -try: - # PIL is only available in python 3.8+ - import numpy as np - import PIL - from PIL.Image import Image as Img +import reflex as rx +from reflex.components.next.image import Image, serialize_image # type: ignore +from reflex.utils.serializers import serialize - import reflex as rx - from reflex.components.media.image import Image, serialize_image # type: ignore - from reflex.utils.serializers import serialize - @pytest.fixture - def pil_image() -> Img: - """Get an image. +@pytest.fixture +def pil_image() -> Img: + """Get an image. - Returns: - A random PIL image. - """ - imarray = np.random.rand(100, 100, 3) * 255 - return PIL.Image.fromarray(imarray.astype("uint8")).convert("RGBA") # type: ignore + Returns: + A random PIL image. + """ + imarray = np.random.rand(100, 100, 3) * 255 + return PIL.Image.fromarray(imarray.astype("uint8")).convert("RGBA") # type: ignore - def test_serialize_image(pil_image: Img): - """Test that serializing an image works. - Args: - pil_image: The image to serialize. - """ - data = serialize(pil_image) - assert isinstance(data, str) - assert data == serialize_image(pil_image) - assert data.startswith("data:image/png;base64,") +def test_serialize_image(pil_image: Img): + """Test that serializing an image works. - def test_set_src_str(): - """Test that setting the src works.""" - image = rx.image(src="pic2.jpeg") - assert str(image.src) == "{`pic2.jpeg`}" # type: ignore + Args: + pil_image: The image to serialize. + """ + data = serialize(pil_image) + assert isinstance(data, str) + assert data == serialize_image(pil_image) + assert data.startswith("data:image/png;base64,") - def test_set_src_img(pil_image: Img): - """Test that setting the src works. - Args: - pil_image: The image to serialize. - """ - image = Image.create(src=pil_image) - assert str(image.src._var_name) == serialize_image(pil_image) # type: ignore +def test_set_src_str(): + """Test that setting the src works.""" + image = rx.image(src="pic2.jpeg") + assert str(image.src) == "{`pic2.jpeg`}" # type: ignore - def test_render(pil_image: Img): - """Test that rendering an image works. - Args: - pil_image: The image to serialize. - """ - image = Image.create(src=pil_image) - assert image.src._var_is_string # type: ignore +def test_set_src_img(pil_image: Img): + """Test that setting the src works. -except ImportError: + Args: + pil_image: The image to serialize. + """ + image = Image.create(src=pil_image) + assert str(image.src._var_name) == serialize_image(pil_image) # type: ignore - def test_pillow_import(): - """Make sure the Python version is less than 3.8.""" - import sys - assert sys.version_info < (3, 8) +def test_render(pil_image: Img): + """Test that rendering an image works. + + Args: + pil_image: The image to serialize. + """ + image = Image.create(src=pil_image) + assert image.src._var_is_string # type: ignore