Refactor upload component and add styled upload component (#3035)
This commit is contained in:
parent
cd1a30d758
commit
ce2bd2286e
@ -49,7 +49,7 @@ def UploadFile():
|
|||||||
id="token",
|
id="token",
|
||||||
),
|
),
|
||||||
rx.heading("Default Upload"),
|
rx.heading("Default Upload"),
|
||||||
rx.upload(
|
rx.upload.root(
|
||||||
rx.vstack(
|
rx.vstack(
|
||||||
rx.button("Select File"),
|
rx.button("Select File"),
|
||||||
rx.text("Drag and drop files here or click to select files"),
|
rx.text("Drag and drop files here or click to select files"),
|
||||||
@ -73,7 +73,7 @@ def UploadFile():
|
|||||||
id="clear_button",
|
id="clear_button",
|
||||||
),
|
),
|
||||||
rx.heading("Secondary Upload"),
|
rx.heading("Secondary Upload"),
|
||||||
rx.upload(
|
rx.upload.root(
|
||||||
rx.vstack(
|
rx.vstack(
|
||||||
rx.button("Select File"),
|
rx.button("Select File"),
|
||||||
rx.text("Drag and drop files here or click to select files"),
|
rx.text("Drag and drop files here or click to select files"),
|
||||||
|
@ -16,7 +16,7 @@ from .responsive import (
|
|||||||
tablet_only,
|
tablet_only,
|
||||||
)
|
)
|
||||||
from .upload import (
|
from .upload import (
|
||||||
Upload,
|
UploadNamespace,
|
||||||
cancel_upload,
|
cancel_upload,
|
||||||
clear_selected_files,
|
clear_selected_files,
|
||||||
get_upload_dir,
|
get_upload_dir,
|
||||||
@ -31,4 +31,4 @@ debounce_input = DebounceInput.create
|
|||||||
foreach = Foreach.create
|
foreach = Foreach.create
|
||||||
html = Html.create
|
html = Html.create
|
||||||
match = Match.create
|
match = Match.create
|
||||||
upload = Upload.create
|
upload = UploadNamespace()
|
||||||
|
@ -9,7 +9,7 @@ from typing import Any, Callable, ClassVar, Dict, List, Optional, Union
|
|||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.components.chakra.forms.input import Input
|
from reflex.components.chakra.forms.input import Input
|
||||||
from reflex.components.chakra.layout.box import Box
|
from reflex.components.chakra.layout.box import Box
|
||||||
from reflex.components.component import Component, MemoizationLeaf
|
from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
|
||||||
from reflex.constants import Dirs
|
from reflex.constants import Dirs
|
||||||
from reflex.event import (
|
from reflex.event import (
|
||||||
CallableEventSpec,
|
CallableEventSpec,
|
||||||
@ -299,3 +299,38 @@ class Upload(MemoizationLeaf):
|
|||||||
return {
|
return {
|
||||||
(5, "UploadFilesProvider"): UploadFilesProvider.create(),
|
(5, "UploadFilesProvider"): UploadFilesProvider.create(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class StyledUpload(Upload):
|
||||||
|
"""The styled Upload Component."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, *children, **props) -> Component:
|
||||||
|
"""Create the styled upload component.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*children: The children of the component.
|
||||||
|
**props: The properties of the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The styled upload component.
|
||||||
|
"""
|
||||||
|
# Set default props.
|
||||||
|
props.setdefault("border", "1px dashed var(--accent-12)")
|
||||||
|
props.setdefault("padding", "5em")
|
||||||
|
props.setdefault("textAlign", "center")
|
||||||
|
|
||||||
|
# Mark the Upload component as used in the app.
|
||||||
|
Upload.is_used = True
|
||||||
|
|
||||||
|
return super().create(
|
||||||
|
*children,
|
||||||
|
**props,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UploadNamespace(ComponentNamespace):
|
||||||
|
"""Upload component namespace."""
|
||||||
|
|
||||||
|
root = Upload.create
|
||||||
|
__call__ = StyledUpload.create
|
||||||
|
@ -13,7 +13,7 @@ from typing import Any, Callable, ClassVar, Dict, List, Optional, Union
|
|||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.components.chakra.forms.input import Input
|
from reflex.components.chakra.forms.input import Input
|
||||||
from reflex.components.chakra.layout.box import Box
|
from reflex.components.chakra.layout.box import Box
|
||||||
from reflex.components.component import Component, MemoizationLeaf
|
from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
|
||||||
from reflex.constants import Dirs
|
from reflex.constants import Dirs
|
||||||
from reflex.event import (
|
from reflex.event import (
|
||||||
CallableEventSpec,
|
CallableEventSpec,
|
||||||
@ -219,3 +219,201 @@ class Upload(MemoizationLeaf):
|
|||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
def get_event_triggers(self) -> dict[str, Union[Var, Any]]: ...
|
def get_event_triggers(self) -> dict[str, Union[Var, Any]]: ...
|
||||||
|
|
||||||
|
class StyledUpload(Upload):
|
||||||
|
@overload
|
||||||
|
@classmethod
|
||||||
|
def create( # type: ignore
|
||||||
|
cls,
|
||||||
|
*children,
|
||||||
|
accept: Optional[
|
||||||
|
Union[Var[Optional[Dict[str, List]]], Optional[Dict[str, List]]]
|
||||||
|
] = None,
|
||||||
|
disabled: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
max_files: Optional[Union[Var[int], int]] = None,
|
||||||
|
max_size: Optional[Union[Var[int], int]] = None,
|
||||||
|
min_size: Optional[Union[Var[int], int]] = None,
|
||||||
|
multiple: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
no_click: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
no_drag: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
no_keyboard: Optional[Union[Var[bool], bool]] = 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_drop: 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
|
||||||
|
) -> "StyledUpload":
|
||||||
|
"""Create the styled upload component.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*children: The children of the component.
|
||||||
|
accept: The list of accepted file types. This should be a dictionary of MIME types as keys and array of file formats as values. supported MIME types: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||||||
|
disabled: Whether the dropzone is disabled.
|
||||||
|
max_files: The maximum number of files that can be uploaded.
|
||||||
|
max_size: The maximum file size (bytes) that can be uploaded.
|
||||||
|
min_size: The minimum file size (bytes) that can be uploaded.
|
||||||
|
multiple: Whether to allow multiple files to be uploaded.
|
||||||
|
no_click: Whether to disable click to upload.
|
||||||
|
no_drag: Whether to disable drag and drop.
|
||||||
|
no_keyboard: Whether to disable using the space/enter keys to upload.
|
||||||
|
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 properties of the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The styled upload component.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
class UploadNamespace(ComponentNamespace):
|
||||||
|
root = Upload.create
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __call__(
|
||||||
|
*children,
|
||||||
|
accept: Optional[
|
||||||
|
Union[Var[Optional[Dict[str, List]]], Optional[Dict[str, List]]]
|
||||||
|
] = None,
|
||||||
|
disabled: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
max_files: Optional[Union[Var[int], int]] = None,
|
||||||
|
max_size: Optional[Union[Var[int], int]] = None,
|
||||||
|
min_size: Optional[Union[Var[int], int]] = None,
|
||||||
|
multiple: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
no_click: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
no_drag: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
no_keyboard: Optional[Union[Var[bool], bool]] = 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_drop: 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
|
||||||
|
) -> "StyledUpload":
|
||||||
|
"""Create the styled upload component.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*children: The children of the component.
|
||||||
|
accept: The list of accepted file types. This should be a dictionary of MIME types as keys and array of file formats as values. supported MIME types: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||||||
|
disabled: Whether the dropzone is disabled.
|
||||||
|
max_files: The maximum number of files that can be uploaded.
|
||||||
|
max_size: The maximum file size (bytes) that can be uploaded.
|
||||||
|
min_size: The minimum file size (bytes) that can be uploaded.
|
||||||
|
multiple: Whether to allow multiple files to be uploaded.
|
||||||
|
no_click: Whether to disable click to upload.
|
||||||
|
no_drag: Whether to disable drag and drop.
|
||||||
|
no_keyboard: Whether to disable using the space/enter keys to upload.
|
||||||
|
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 properties of the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The styled upload component.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from reflex.components.core.upload import (
|
from reflex.components.core.upload import (
|
||||||
|
StyledUpload,
|
||||||
Upload,
|
Upload,
|
||||||
|
UploadNamespace,
|
||||||
_on_drop_spec, # type: ignore
|
_on_drop_spec, # type: ignore
|
||||||
cancel_upload,
|
cancel_upload,
|
||||||
get_upload_url,
|
get_upload_url,
|
||||||
@ -77,3 +79,47 @@ def test_upload_create():
|
|||||||
)
|
)
|
||||||
assert isinstance(up_comp_4, Upload)
|
assert isinstance(up_comp_4, Upload)
|
||||||
assert up_comp_4.is_used
|
assert up_comp_4.is_used
|
||||||
|
|
||||||
|
|
||||||
|
def test_styled_upload_create():
|
||||||
|
styled_up_comp_1 = StyledUpload.create()
|
||||||
|
assert isinstance(styled_up_comp_1, StyledUpload)
|
||||||
|
assert styled_up_comp_1.is_used
|
||||||
|
|
||||||
|
# reset is_used
|
||||||
|
StyledUpload.is_used = False
|
||||||
|
|
||||||
|
styled_up_comp_2 = StyledUpload.create(
|
||||||
|
id="foo_id",
|
||||||
|
on_drop=TestUploadState.drop_handler([]), # type: ignore
|
||||||
|
)
|
||||||
|
assert isinstance(styled_up_comp_2, StyledUpload)
|
||||||
|
assert styled_up_comp_2.is_used
|
||||||
|
|
||||||
|
# reset is_used
|
||||||
|
StyledUpload.is_used = False
|
||||||
|
|
||||||
|
styled_up_comp_3 = StyledUpload.create(
|
||||||
|
id="foo_id",
|
||||||
|
on_drop=TestUploadState.drop_handler,
|
||||||
|
)
|
||||||
|
assert isinstance(styled_up_comp_3, StyledUpload)
|
||||||
|
assert styled_up_comp_3.is_used
|
||||||
|
|
||||||
|
# reset is_used
|
||||||
|
StyledUpload.is_used = False
|
||||||
|
|
||||||
|
styled_up_comp_4 = StyledUpload.create(
|
||||||
|
id="foo_id",
|
||||||
|
on_drop=TestUploadState.not_drop_handler([]), # type: ignore
|
||||||
|
)
|
||||||
|
assert isinstance(styled_up_comp_4, StyledUpload)
|
||||||
|
assert styled_up_comp_4.is_used
|
||||||
|
|
||||||
|
|
||||||
|
def test_upload_namespace():
|
||||||
|
up_ns = UploadNamespace()
|
||||||
|
assert isinstance(up_ns, UploadNamespace)
|
||||||
|
|
||||||
|
assert isinstance(up_ns(id="foo_id"), StyledUpload)
|
||||||
|
assert isinstance(up_ns.root(id="foo_id"), Upload)
|
||||||
|
@ -3,6 +3,24 @@ import pytest
|
|||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def upload_root_component():
|
||||||
|
"""A test upload component function.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A test upload component function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def upload_root_component():
|
||||||
|
return rx.upload.root(
|
||||||
|
rx.button("select file"),
|
||||||
|
rx.text("Drag and drop files here or click to select files"),
|
||||||
|
border="1px dotted black",
|
||||||
|
)
|
||||||
|
|
||||||
|
return upload_root_component()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def upload_component():
|
def upload_component():
|
||||||
"""A test upload component function.
|
"""A test upload component function.
|
||||||
@ -41,13 +59,13 @@ def upload_component_with_props():
|
|||||||
return upload_component_with_props()
|
return upload_component_with_props()
|
||||||
|
|
||||||
|
|
||||||
def test_upload_component_render(upload_component):
|
def test_upload_root_component_render(upload_root_component):
|
||||||
"""Test that the render function is set correctly.
|
"""Test that the render function is set correctly.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
upload_component: component fixture
|
upload_root_component: component fixture
|
||||||
"""
|
"""
|
||||||
upload = upload_component.render()
|
upload = upload_root_component.render()
|
||||||
|
|
||||||
# upload
|
# upload
|
||||||
assert upload["name"] == "ReactDropzone"
|
assert upload["name"] == "ReactDropzone"
|
||||||
@ -82,6 +100,47 @@ def test_upload_component_render(upload_component):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_upload_component_render(upload_component):
|
||||||
|
"""Test that the render function is set correctly.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
upload_component: component fixture
|
||||||
|
"""
|
||||||
|
upload = upload_component.render()
|
||||||
|
|
||||||
|
# upload
|
||||||
|
assert upload["name"] == "ReactDropzone"
|
||||||
|
assert upload["props"] == [
|
||||||
|
"id={`default`}",
|
||||||
|
"multiple={true}",
|
||||||
|
"onDrop={e => setFilesById(filesById => ({...filesById, default: e}))}",
|
||||||
|
"ref={ref_default}",
|
||||||
|
]
|
||||||
|
assert upload["args"] == ("getRootProps", "getInputProps")
|
||||||
|
|
||||||
|
# box inside of upload
|
||||||
|
[box] = upload["children"]
|
||||||
|
assert box["name"] == "Box"
|
||||||
|
assert box["props"] == [
|
||||||
|
'sx={{"border": "1px dotted black", "padding": "5em", "textAlign": "center"}}',
|
||||||
|
"{...getRootProps()}",
|
||||||
|
]
|
||||||
|
|
||||||
|
# input, button and text inside of box
|
||||||
|
[input, button, text] = box["children"]
|
||||||
|
assert input["name"] == "Input"
|
||||||
|
assert input["props"] == ["type={`file`}", "{...getInputProps()}"]
|
||||||
|
|
||||||
|
assert button["name"] == "RadixThemesButton"
|
||||||
|
assert button["children"][0]["contents"] == "{`select file`}"
|
||||||
|
|
||||||
|
assert text["name"] == "RadixThemesText"
|
||||||
|
assert (
|
||||||
|
text["children"][0]["contents"]
|
||||||
|
== "{`Drag and drop files here or click to select files`}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_upload_component_with_props_render(upload_component_with_props):
|
def test_upload_component_with_props_render(upload_component_with_props):
|
||||||
"""Test that the render function is set correctly.
|
"""Test that the render function is set correctly.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user