upload.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. """A file upload component."""
  2. from __future__ import annotations
  3. from typing import Any, Dict, List, Optional, Union
  4. from reflex.components.component import Component
  5. from reflex.components.forms.input import Input
  6. from reflex.components.layout.box import Box
  7. from reflex.constants import EventTriggers
  8. from reflex.event import EventChain
  9. from reflex.vars import BaseVar, Var
  10. files_state: str = "const [files, setFiles] = useState([]);"
  11. upload_file: BaseVar = BaseVar(name="e => setFiles((files) => e)", type_=EventChain)
  12. # Use this var along with the Upload component to render the list of selected files.
  13. selected_files: BaseVar = BaseVar(name="files.map((f) => f.name)", type_=List[str])
  14. clear_selected_files: BaseVar = BaseVar(
  15. name="_e => setFiles((files) => [])", type_=EventChain
  16. )
  17. class Upload(Component):
  18. """A file upload component."""
  19. library = "react-dropzone@^14.2.3"
  20. tag = "ReactDropzone"
  21. is_default = True
  22. # The list of accepted file types. This should be a dictionary of MIME types as keys and array of file formats as
  23. # values.
  24. # supported MIME types: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
  25. accept: Var[Optional[Dict[str, List]]]
  26. # Whether the dropzone is disabled.
  27. disabled: Var[bool]
  28. # The maximum number of files that can be uploaded.
  29. max_files: Var[int]
  30. # The maximum file size (bytes) that can be uploaded.
  31. max_size: Var[int]
  32. # The minimum file size (bytes) that can be uploaded.
  33. min_size: Var[int]
  34. # Whether to allow multiple files to be uploaded.
  35. multiple: Var[bool] = True # type: ignore
  36. # Whether to disable click to upload.
  37. no_click: Var[bool]
  38. # Whether to disable drag and drop.
  39. no_drag: Var[bool]
  40. # Whether to disable using the space/enter keys to upload.
  41. no_keyboard: Var[bool]
  42. @classmethod
  43. def create(cls, *children, **props) -> Component:
  44. """Create an upload component.
  45. Args:
  46. *children: The children of the component.
  47. **props: The properties of the component.
  48. Returns:
  49. The upload component.
  50. """
  51. # get only upload component props
  52. supported_props = cls.get_props()
  53. upload_props = {
  54. key: value for key, value in props.items() if key in supported_props
  55. }
  56. # The file input to use.
  57. upload = Input.create(type_="file")
  58. upload.special_props = {BaseVar(name="{...getInputProps()}", type_=None)}
  59. # The dropzone to use.
  60. zone = Box.create(
  61. upload,
  62. *children,
  63. **{k: v for k, v in props.items() if k not in supported_props},
  64. )
  65. zone.special_props = {BaseVar(name="{...getRootProps()}", type_=None)}
  66. # Create the component.
  67. return super().create(zone, on_drop=upload_file, **upload_props)
  68. def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
  69. """Get the event triggers that pass the component's value to the handler.
  70. Returns:
  71. A dict mapping the event trigger to the var that is passed to the handler.
  72. """
  73. return {
  74. **super().get_event_triggers(),
  75. EventTriggers.ON_DROP: lambda e0: [e0],
  76. }
  77. def _render(self):
  78. out = super()._render()
  79. out.args = ("getRootProps", "getInputProps")
  80. return out
  81. def _get_hooks(self) -> str | None:
  82. return (super()._get_hooks() or "") + files_state