Skip to content

Commit

Permalink
Expose on_drop event trigger for rx.upload component. (#2766)
Browse files Browse the repository at this point in the history
* Expose `on_drop` event trigger for rx.upload component.

If `on_drop` is provided, it should be an EventSpec accepting the special
rx.upload_files() arg.

When `on_drop` is provided, it will be called immediately when files are
selected. The default functionality of saving a file list to be uploaded later
will not be available.

* update pyi file

* Undeprecate explicit EventChain
  • Loading branch information
masenf authored Mar 5, 2024
1 parent 75b63cb commit c79719f
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 16 deletions.
9 changes: 0 additions & 9 deletions reflex/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,21 +374,12 @@ def _create_event_chain(

arg_spec = triggers.get(event_trigger, lambda: [])

wrapped = False
# If the input is a single event handler, wrap it in a list.
if isinstance(value, (EventHandler, EventSpec)):
wrapped = True
value = [value]

# If the input is a list of event handlers, create an event chain.
if isinstance(value, List):
if not wrapped:
console.deprecate(
feature_name="EventChain",
reason="to avoid confusion, only use yield API",
deprecation_version="0.2.8",
removal_version="0.5.0",
)
events: list[EventSpec] = []
for v in value:
if isinstance(v, EventHandler):
Expand Down
63 changes: 58 additions & 5 deletions reflex/components/core/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@

import os
from pathlib import Path
from typing import Any, ClassVar, Dict, List, Optional, Union
from typing import Any, Callable, ClassVar, Dict, List, Optional, Union

from reflex import constants
from reflex.components.chakra.forms.input import Input
from reflex.components.chakra.layout.box import Box
from reflex.components.component import Component, MemoizationLeaf
from reflex.constants import Dirs
from reflex.event import CallableEventSpec, EventChain, EventSpec, call_script
from reflex.event import (
CallableEventSpec,
EventChain,
EventSpec,
call_event_fn,
call_script,
parse_args_spec,
)
from reflex.utils import imports
from reflex.vars import BaseVar, CallableVar, Var, VarData

Expand Down Expand Up @@ -135,6 +142,18 @@ def get_upload_url(file_path: str) -> str:
return f"{uploaded_files_url_prefix}/{file_path}"


def _on_drop_spec(files: Var):
"""Args spec for the on_drop event trigger.
Args:
files: The files to upload.
Returns:
Signature for on_drop handler including the files to upload.
"""
return [files]


class UploadFilesProvider(Component):
"""AppWrap component that provides a dict of selected files by ID via useContext."""

Expand Down Expand Up @@ -198,7 +217,7 @@ def create(cls, *children, **props) -> Component:
cls.is_used = True

# get only upload component props
supported_props = cls.get_props()
supported_props = cls.get_props().union({"on_drop"})
upload_props = {
key: value for key, value in props.items() if key in supported_props
}
Expand All @@ -218,8 +237,27 @@ def create(cls, *children, **props) -> Component:

# Create the component.
upload_props["id"] = props.get("id", DEFAULT_UPLOAD_ID)

if upload_props.get("on_drop") is None:
# If on_drop is not provided, save files to be uploaded later.
upload_props["on_drop"] = upload_file(upload_props["id"])
else:
on_drop = upload_props["on_drop"]
if isinstance(on_drop, Callable):
# Call the lambda to get the event chain.
on_drop = call_event_fn(on_drop, _on_drop_spec) # type: ignore
if isinstance(on_drop, EventSpec):
# Update the provided args for direct use with on_drop.
on_drop = on_drop.with_args(
args=tuple(
cls._update_arg_tuple_for_on_drop(arg_value)
for arg_value in on_drop.args
),
)
upload_props["on_drop"] = on_drop
return super().create(
zone, on_drop=upload_file(upload_props["id"]), **upload_props
zone,
**upload_props,
)

def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
Expand All @@ -230,9 +268,24 @@ def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
"""
return {
**super().get_event_triggers(),
constants.EventTriggers.ON_DROP: lambda e0: [e0],
constants.EventTriggers.ON_DROP: _on_drop_spec,
}

@classmethod
def _update_arg_tuple_for_on_drop(cls, arg_value: tuple[Var, Var]):
"""Helper to update caller-provided EventSpec args for direct use with on_drop.
Args:
arg_value: The arg tuple to update (if necessary).
Returns:
The updated arg_value tuple when arg is "files", otherwise the original arg_value.
"""
if arg_value[0]._var_name == "files":
placeholder = parse_args_spec(_on_drop_spec)[0]
return (arg_value[0], placeholder)
return arg_value

def _render(self):
out = super()._render()
out.args = ("getRootProps", "getInputProps")
Expand Down
11 changes: 9 additions & 2 deletions reflex/components/core/upload.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@ from reflex.event import EventChain, EventHandler, EventSpec
from reflex.style import Style
import os
from pathlib import Path
from typing import Any, ClassVar, Dict, List, Optional, Union
from typing import Any, Callable, ClassVar, Dict, List, Optional, Union
from reflex import constants
from reflex.components.chakra.forms.input import Input
from reflex.components.chakra.layout.box import Box
from reflex.components.component import Component, MemoizationLeaf
from reflex.constants import Dirs
from reflex.event import CallableEventSpec, EventChain, EventSpec, call_script
from reflex.event import (
CallableEventSpec,
EventChain,
EventSpec,
call_event_fn,
call_script,
parse_args_spec,
)
from reflex.utils import imports
from reflex.vars import BaseVar, CallableVar, Var, VarData

Expand Down

0 comments on commit c79719f

Please sign in to comment.