upload.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. """A file upload component."""
  2. from __future__ import annotations
  3. from typing import Any, Dict, List, Optional, Union
  4. from reflex import constants
  5. from reflex.components.component import Component
  6. from reflex.components.forms.input import Input
  7. from reflex.components.layout.box import Box
  8. from reflex.constants import Dirs
  9. from reflex.event import CallableEventSpec, EventChain, EventSpec, call_script
  10. from reflex.utils import imports
  11. from reflex.vars import BaseVar, CallableVar, Var, VarData
  12. DEFAULT_UPLOAD_ID: str = "default"
  13. @CallableVar
  14. def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> BaseVar:
  15. """Get the file upload drop trigger.
  16. This var is passed to the dropzone component to update the file list when a
  17. drop occurs.
  18. Args:
  19. id_: The id of the upload to get the drop trigger for.
  20. Returns:
  21. A var referencing the file upload drop trigger.
  22. """
  23. return BaseVar(
  24. _var_name=f"e => upload_files.{id_}[1]((files) => e)",
  25. _var_type=EventChain,
  26. _var_data=VarData( # type: ignore
  27. imports={
  28. f"/{Dirs.STATE_PATH}": {
  29. imports.ImportVar(tag="upload_files"),
  30. },
  31. },
  32. ),
  33. )
  34. @CallableVar
  35. def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> BaseVar:
  36. """Get the list of selected files.
  37. Args:
  38. id_: The id of the upload to get the selected files for.
  39. Returns:
  40. A var referencing the list of selected file paths.
  41. """
  42. return BaseVar(
  43. _var_name=f"(upload_files.{id_} ? upload_files.{id_}[0]?.map((f) => (f.path || f.name)) : [])",
  44. _var_type=List[str],
  45. _var_data=VarData( # type: ignore
  46. imports={
  47. f"/{Dirs.STATE_PATH}": {
  48. imports.ImportVar(tag="upload_files"),
  49. },
  50. },
  51. ),
  52. )
  53. @CallableEventSpec
  54. def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec:
  55. """Clear the list of selected files.
  56. Args:
  57. id_: The id of the upload to clear.
  58. Returns:
  59. An event spec that clears the list of selected files when triggered.
  60. """
  61. return call_script(f"upload_files.{id_}[1]((files) => [])")
  62. def cancel_upload(upload_id: str) -> EventSpec:
  63. """Cancel an upload.
  64. Args:
  65. upload_id: The id of the upload to cancel.
  66. Returns:
  67. An event spec that cancels the upload when triggered.
  68. """
  69. return call_script(f"upload_controllers[{upload_id!r}]?.abort()")
  70. class Upload(Component):
  71. """A file upload component."""
  72. library = "react-dropzone@14.2.3"
  73. tag = "ReactDropzone"
  74. is_default = True
  75. # The list of accepted file types. This should be a dictionary of MIME types as keys and array of file formats as
  76. # values.
  77. # supported MIME types: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
  78. accept: Var[Optional[Dict[str, List]]]
  79. # Whether the dropzone is disabled.
  80. disabled: Var[bool]
  81. # The maximum number of files that can be uploaded.
  82. max_files: Var[int]
  83. # The maximum file size (bytes) that can be uploaded.
  84. max_size: Var[int]
  85. # The minimum file size (bytes) that can be uploaded.
  86. min_size: Var[int]
  87. # Whether to allow multiple files to be uploaded.
  88. multiple: Var[bool] = True # type: ignore
  89. # Whether to disable click to upload.
  90. no_click: Var[bool]
  91. # Whether to disable drag and drop.
  92. no_drag: Var[bool]
  93. # Whether to disable using the space/enter keys to upload.
  94. no_keyboard: Var[bool]
  95. @classmethod
  96. def create(cls, *children, **props) -> Component:
  97. """Create an upload component.
  98. Args:
  99. *children: The children of the component.
  100. **props: The properties of the component.
  101. Returns:
  102. The upload component.
  103. """
  104. # get only upload component props
  105. supported_props = cls.get_props()
  106. upload_props = {
  107. key: value for key, value in props.items() if key in supported_props
  108. }
  109. # The file input to use.
  110. upload = Input.create(type_="file")
  111. upload.special_props = {
  112. BaseVar(_var_name="{...getInputProps()}", _var_type=None)
  113. }
  114. # The dropzone to use.
  115. zone = Box.create(
  116. upload,
  117. *children,
  118. **{k: v for k, v in props.items() if k not in supported_props},
  119. )
  120. zone.special_props = {BaseVar(_var_name="{...getRootProps()}", _var_type=None)}
  121. # Create the component.
  122. upload_props["id"] = props.get("id", DEFAULT_UPLOAD_ID)
  123. return super().create(
  124. zone, on_drop=upload_file(upload_props["id"]), **upload_props
  125. )
  126. def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
  127. """Get the event triggers that pass the component's value to the handler.
  128. Returns:
  129. A dict mapping the event trigger to the var that is passed to the handler.
  130. """
  131. return {
  132. **super().get_event_triggers(),
  133. constants.EventTriggers.ON_DROP: lambda e0: [e0],
  134. }
  135. def _render(self):
  136. out = super()._render()
  137. out.args = ("getRootProps", "getInputProps")
  138. return out
  139. def _get_hooks(self) -> str | None:
  140. return (
  141. super()._get_hooks() or ""
  142. ) + f"upload_files.{self.id or DEFAULT_UPLOAD_ID} = useState([]);"
  143. def _get_imports(self) -> imports.ImportDict:
  144. return imports.merge_imports(
  145. super()._get_imports(),
  146. {
  147. "react": {imports.ImportVar(tag="useState")},
  148. f"/{constants.Dirs.STATE_PATH}": [
  149. imports.ImportVar(tag="upload_files")
  150. ],
  151. },
  152. )