11import threading
22import typing
3- from typing import Callable , List , Optional , cast
3+ from typing import Callable , List , Optional , Union , cast
44
5- import ipyvuetify
65import traitlets
76from ipyvue import Template
87from ipyvuetify .extra import FileInput
@@ -30,40 +29,22 @@ class FileDropZone(FileInput):
3029
3130
3231@solara .component
33- def FileDrop (
34- label = "Drop file here" ,
32+ def _FileDrop (
33+ label = "Drop file(s) here" ,
3534 on_total_progress : Optional [Callable [[float ], None ]] = None ,
36- on_file : Optional [Callable [[FileInfo ], None ]] = None ,
35+ on_file : Optional [Callable [[Union [ FileInfo , List [ FileInfo ]] ], None ]] = None ,
3736 lazy : bool = True ,
38- ) -> ipyvuetify .extra .FileInput :
39- """Region a user can drop a file into for file uploading.
40-
41- If lazy=True, no file content will be loaded into memory,
42- nor will any data be transferred by default.
43- If lazy=False, file content will be loaded into memory and passed to the `on_file` callback via the `FileInfo.data` attribute.
44-
45-
46- A file object is of the following argument type:
47- ```python
48- class FileInfo(typing.TypedDict):
49- name: str # file name
50- size: int # file size in bytes
51- file_obj: typing.BinaryIO
52- data: Optional[bytes]: bytes # only present if lazy=False
53- ```
54-
55-
56- ## Arguments
57- * `on_total_progress`: Will be called with the progress in % of the file upload.
58- * `on_file`: Will be called with a `FileInfo` object, which contains the file `.name`, `.length` and a `.file_obj` object.
59- * `lazy`: Whether to load the file contents into memory or not. If `False`,
60- the file contents will be loaded into memory via the `.data` attribute of file object(s).
37+ multiple : bool = False ,
38+ ):
39+ """Generic implementation used by FileDrop and FileDropMultiple.
6140
41+ If multiple=True, multiple files can be uploaded.
6242 """
43+
6344 file_info , set_file_info = solara .use_state (None )
6445 wired_files , set_wired_files = solara .use_state (cast (Optional [typing .List [FileInfo ]], None ))
6546
66- file_drop = FileDropZone .element (label = label , on_total_progress = on_total_progress , on_file_info = set_file_info , multiple = False ) # type: ignore
47+ file_drop = FileDropZone .element (label = label , on_total_progress = on_total_progress , on_file_info = set_file_info , multiple = multiple ) # type: ignore
6748
6849 def wire_files ():
6950 if not file_info :
@@ -83,11 +64,15 @@ def handle_file(cancel: threading.Event):
8364 if not wired_files :
8465 return
8566 if on_file :
86- if not lazy :
87- wired_files [0 ]["data" ] = wired_files [0 ]["file_obj" ].read ()
67+ for i in range (len (wired_files )):
68+ if not lazy :
69+ wired_files [i ]["data" ] = wired_files [i ]["file_obj" ].read ()
70+ else :
71+ wired_files [i ]["data" ] = None
72+ if multiple :
73+ on_file (wired_files )
8874 else :
89- wired_files [0 ]["data" ] = None
90- on_file (wired_files [0 ])
75+ on_file (wired_files [0 ])
9176
9277 result : solara .Result = hooks .use_thread (handle_file , [wired_files ])
9378 if result .error :
@@ -96,16 +81,51 @@ def handle_file(cancel: threading.Event):
9681 return file_drop
9782
9883
84+ @solara .component
85+ def FileDrop (
86+ label = "Drop file here" ,
87+ on_total_progress : Optional [Callable [[float ], None ]] = None ,
88+ on_file : Optional [Callable [[FileInfo ], None ]] = None ,
89+ lazy : bool = True ,
90+ ):
91+ """Region a user can drop a file into for file uploading.
92+
93+ If lazy=True, no file content will be loaded into memory,
94+ nor will any data be transferred by default.
95+ If lazy=False, file content will be loaded into memory and passed to the `on_file` callback via the `FileInfo.data` attribute.
96+
97+
98+ A file object is of the following argument type:
99+ ```python
100+ class FileInfo(typing.TypedDict):
101+ name: str # file name
102+ size: int # file size in bytes
103+ file_obj: typing.BinaryIO
104+ data: Optional[bytes]: bytes # only present if lazy=False
105+ ```
106+
107+
108+ ## Arguments
109+ * `on_total_progress`: Will be called with the progress in % of the file upload.
110+ * `on_file`: Will be called with a `FileInfo` object, which contains the file `.name`, `.length` and a `.file_obj` object.
111+ * `lazy`: Whether to load the file contents into memory or not. If `False`,
112+ the file contents will be loaded into memory via the `.data` attribute of file object(s).
113+
114+ """
115+
116+ return _FileDrop (label = label , on_total_progress = on_total_progress , on_file = on_file , lazy = lazy , multiple = False )
117+
118+
99119@solara .component
100120def FileDropMultiple (
101121 label = "Drop files here" ,
102122 on_total_progress : Optional [Callable [[float ], None ]] = None ,
103123 on_file : Optional [Callable [[List [FileInfo ]], None ]] = None ,
104124 lazy : bool = True ,
105- ) -> ipyvuetify . extra . FileInput :
125+ ):
106126 """Region a user can drop multiple files into for file uploading.
107127
108- Almost identical to `FileDrop` except that `on_file` is called
128+ Almost identical to `FileDrop` except that multiple files can be dropped and `on_file` is called
109129 with a list of `FileInfo` objects.
110130
111131 ## Arguments
@@ -115,38 +135,5 @@ def FileDropMultiple(
115135 * `lazy`: Whether to load the file contents into memory or not.
116136
117137 """
118- file_info , set_file_info = solara .use_state (None )
119- wired_files , set_wired_files = solara .use_state (cast (Optional [typing .List [FileInfo ]], None ))
120-
121- file_drop = FileDropZone .element (label = label , on_total_progress = on_total_progress , on_file_info = set_file_info , multiple = True ) # type: ignore
122-
123- def wire_files ():
124- if not file_info :
125- return
126-
127- real = cast (FileDropZone , solara .get_widget (file_drop ))
128-
129- # workaround for @observe being cleared
130- real .version += 1
131- real .reset_stats ()
132-
133- set_wired_files (cast (typing .List [FileInfo ], real .get_files ()))
134-
135- solara .use_side_effect (wire_files , [file_info ])
136-
137- def handle_file (cancel : threading .Event ):
138- if not wired_files :
139- return
140- if on_file :
141- for i in range (len (wired_files )):
142- if not lazy :
143- wired_files [i ]["data" ] = wired_files [i ]["file_obj" ].read ()
144- else :
145- wired_files [i ]["data" ] = None
146- on_file (wired_files )
147-
148- result : solara .Result = hooks .use_thread (handle_file , [wired_files ])
149- if result .error :
150- raise result .error
151138
152- return file_drop
139+ return _FileDrop ( label = label , on_total_progress = on_total_progress , on_file = on_file , lazy = lazy , multiple = True )
0 commit comments