Browse Source

Expose `on_drop` event trigger for rx.upload component. (#2766)

* 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
Masen Furer 1 year ago
parent
commit
c79719f7be

+ 0 - 9
reflex/components/component.py

@@ -374,21 +374,12 @@ class Component(BaseComponent, ABC):
 
 
         arg_spec = triggers.get(event_trigger, lambda: [])
         arg_spec = triggers.get(event_trigger, lambda: [])
 
 
-        wrapped = False
         # If the input is a single event handler, wrap it in a list.
         # If the input is a single event handler, wrap it in a list.
         if isinstance(value, (EventHandler, EventSpec)):
         if isinstance(value, (EventHandler, EventSpec)):
-            wrapped = True
             value = [value]
             value = [value]
 
 
         # If the input is a list of event handlers, create an event chain.
         # If the input is a list of event handlers, create an event chain.
         if isinstance(value, List):
         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] = []
             events: list[EventSpec] = []
             for v in value:
             for v in value:
                 if isinstance(v, EventHandler):
                 if isinstance(v, EventHandler):

+ 58 - 5
reflex/components/core/upload.py

@@ -3,14 +3,21 @@ from __future__ import annotations
 
 
 import os
 import os
 from pathlib import Path
 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 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, MemoizationLeaf
 from reflex.constants import Dirs
 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.utils import imports
 from reflex.vars import BaseVar, CallableVar, Var, VarData
 from reflex.vars import BaseVar, CallableVar, Var, VarData
 
 
@@ -135,6 +142,18 @@ def get_upload_url(file_path: str) -> str:
     return f"{uploaded_files_url_prefix}/{file_path}"
     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):
 class UploadFilesProvider(Component):
     """AppWrap component that provides a dict of selected files by ID via useContext."""
     """AppWrap component that provides a dict of selected files by ID via useContext."""
 
 
@@ -198,7 +217,7 @@ class Upload(MemoizationLeaf):
         cls.is_used = True
         cls.is_used = True
 
 
         # get only upload component props
         # get only upload component props
-        supported_props = cls.get_props()
+        supported_props = cls.get_props().union({"on_drop"})
         upload_props = {
         upload_props = {
             key: value for key, value in props.items() if key in supported_props
             key: value for key, value in props.items() if key in supported_props
         }
         }
@@ -218,8 +237,27 @@ class Upload(MemoizationLeaf):
 
 
         # Create the component.
         # Create the component.
         upload_props["id"] = props.get("id", DEFAULT_UPLOAD_ID)
         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(
         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]]:
     def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
@@ -230,9 +268,24 @@ class Upload(MemoizationLeaf):
         """
         """
         return {
         return {
             **super().get_event_triggers(),
             **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):
     def _render(self):
         out = super()._render()
         out = super()._render()
         out.args = ("getRootProps", "getInputProps")
         out.args = ("getRootProps", "getInputProps")

+ 9 - 2
reflex/components/core/upload.pyi

@@ -9,13 +9,20 @@ from reflex.event import EventChain, EventHandler, EventSpec
 from reflex.style import Style
 from reflex.style import Style
 import os
 import os
 from pathlib import Path
 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 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, MemoizationLeaf
 from reflex.constants import Dirs
 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.utils import imports
 from reflex.vars import BaseVar, CallableVar, Var, VarData
 from reflex.vars import BaseVar, CallableVar, Var, VarData