jinya_ui/widgets/form/
file_upload.rs1use yew::prelude::*;
2use yew::services::reader::File;
3use yew::{Callback, Component, ComponentLink, Html};
4
5pub fn get_css<'a>() -> &'a str {
6 "
8.jinya-file-upload__color-container--default {
9 --state-color: var(--primary-color);
10}
11
12.jinya-file-upload__color-container--negative {
13 --state-color: var(--negative-color);
14}
15
16.jinya-file-upload__color-container--positive {
17 --state-color: var(--positive-color);
18}
19
20.jinya-file-upload__color-container--disabled {
21 --state-color: var(--disabled-border-color);
22}
23
24.jinya-file-upload__color-container--drag-over .jinya-file-upload__label,
25.jinya-file-upload__color-container--drag-over .jinya-file-upload__file-info,
26.jinya-file-upload__color-container--drag-over .jinya-file-upload__container {
27 background-color: var(--input-background-color);
28}
29
30.jinya-file-upload__color-container {
31 display: flex;
32 position: relative;
33 width: 100%;
34 flex: 0 0 100%;
35 flex-flow: row wrap;
36}
37
38.jinya-file-upload__container {
39 display: flex;
40 border: 2px solid var(--state-color);
41 border-radius: 5px;
42 padding: 0.5rem 0.75rem 0.25rem;
43 position: relative;
44 margin-top: 0.75rem;
45 width: 100%;
46 box-sizing: border-box;
47}
48
49.jinya-file-upload__input {
50 display: none;
51}
52
53.jinya-file-upload__file-info {
54 font-size: var(--font-size-16);
55 color: var(--state-color);
56 background: var(--white);
57 border: none;
58 padding: 0;
59 width: 100%;
60}
61
62.jinya-file-upload__file-info:disabled {
63 cursor: not-allowed;
64}
65
66.jinya-file-upload__label {
67 display: block;
68 font-size: var(--font-size-12);
69 color: var(--state-color);
70 position: absolute;
71 top: -0.75rem;
72 background: var(--white);
73 padding-left: 0.25rem;
74 padding-right: 0.25rem;
75 box-sizing: border-box;
76 left: 0.5rem;
77 z-index: 0;
78}
79
80.jinya-file-upload__validation-message {
81 display: block;
82 font-size: var(--font-size-12);
83 color: var(--state-color);
84}
85
86.jinya-file-upload__button {
87 margin-left: auto;
88 font-size: var(--font-size-24);
89 border: 2px solid var(--state-color);
90 height: calc(100% - 2rem);
91 top: 0.75rem;
92 position: absolute;
93 right: 0;
94 padding: 0.5rem;
95 transition: color 0.3s, background 0.3s;
96 background: var(--white);
97 border-bottom-right-radius: 5px;
98 border-top-right-radius: 5px;
99 cursor: pointer;
100}
101
102.jinya-file-upload__button:hover {
103 color: var(--white);
104 background: var(--state-color);
105}
106"
107}
108
109#[derive(Clone, PartialEq)]
110pub enum FileUploadState {
111 Default,
112 Negative,
113 Positive,
114}
115
116pub struct FileUpload {
117 link: ComponentLink<Self>,
118 label: String,
119 state: FileUploadState,
120 validation_message: String,
121 placeholder: String,
122 filename: String,
123 disabled: bool,
124 on_select: Callback<Vec<File>>,
125 is_drag_over: bool,
126 multiple: bool,
127}
128
129#[derive(Clone, PartialEq, Properties)]
130pub struct FileUploadProps {
131 pub label: String,
132 #[prop_or(FileUploadState::Default)]
133 pub state: FileUploadState,
134 #[prop_or("".to_string())]
135 pub validation_message: String,
136 #[prop_or("".to_string())]
137 pub placeholder: String,
138 #[prop_or(false)]
139 pub disabled: bool,
140 #[prop_or(false)]
141 pub multiple: bool,
142 pub on_select: Callback<Vec<File>>,
143 #[prop_or("".to_string())]
144 pub filename: String,
145}
146
147pub enum Msg {
148 Files(Vec<File>),
149 Drop(DragEvent),
150 DragOver(DragEvent),
151 DragExit,
152}
153
154impl Default for FileUploadState {
155 fn default() -> Self {
156 FileUploadState::Default
157 }
158}
159
160impl FileUpload {
161 fn get_input_container_class(&self) -> String {
162 let class = if self.disabled {
163 "jinya-file-upload__color-container jinya-file-upload__color-container--disabled"
164 .to_string()
165 } else {
166 match self.state {
167 FileUploadState::Default => "jinya-file-upload__color-container jinya-file-upload__color-container--default",
168 FileUploadState::Negative => "jinya-file-upload__color-container jinya-file-upload__color-container--negative",
169 FileUploadState::Positive => "jinya-file-upload__color-container jinya-file-upload__color-container--positive",
170 }.to_string()
171 };
172
173 if self.is_drag_over {
174 format!("{} jinya-file-upload__color-container--drag-over", class)
175 } else {
176 class
177 }
178 }
179}
180
181impl Component for FileUpload {
182 type Message = Msg;
183 type Properties = FileUploadProps;
184
185 fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
186 FileUpload {
187 link,
188 label: props.label,
189 state: props.state,
190 validation_message: props.validation_message,
191 placeholder: props.placeholder,
192 disabled: props.disabled,
193 on_select: props.on_select,
194 filename: props.filename,
195 is_drag_over: false,
196 multiple: props.multiple,
197 }
198 }
199
200 fn update(&mut self, msg: Self::Message) -> bool {
201 match msg {
202 Msg::Files(value) => {
203 self.on_select.emit(value.clone());
204 if !value.is_empty() {
205 self.filename = if self.multiple {
206 value
207 .iter()
208 .map(|file| file.name())
209 .collect::<Vec<String>>()
210 .join(", ")
211 } else {
212 value.first().unwrap().name()
213 }
214 }
215 }
216 Msg::Drop(event) => {
217 self.is_drag_over = false;
218 event.prevent_default();
219 event.stop_propagation();
220 let data_transfer = event.data_transfer().unwrap();
221 let files: Vec<File> = js_sys::try_iter(&data_transfer.files().unwrap())
222 .unwrap()
223 .unwrap()
224 .map(|v| File::from(v.unwrap()))
225 .collect();
226 if !files.is_empty() {
227 self.on_select.emit(files.clone());
228 self.filename = if self.multiple {
229 files
230 .iter()
231 .map(|file| file.name())
232 .collect::<Vec<String>>()
233 .join(", ")
234 } else {
235 files.first().unwrap().name()
236 }
237 }
238 }
239 Msg::DragOver(event) => {
240 self.is_drag_over = true;
241 event.prevent_default();
242 event.stop_propagation();
243 let data_transfer = event.data_transfer().unwrap();
244 data_transfer.set_drop_effect("copy");
245 }
246 Msg::DragExit => {
247 self.is_drag_over = false;
248 }
249 }
250
251 true
252 }
253
254 fn change(&mut self, _props: Self::Properties) -> bool {
255 self.label = _props.label;
256 self.state = _props.state;
257 self.validation_message = _props.validation_message;
258 self.placeholder = _props.placeholder;
259 self.disabled = _props.disabled;
260
261 true
262 }
263
264 fn view(&self) -> Html {
265 let id = super::super::super::id_generator::generate_id();
266 html! {
267 <div
268 class=self.get_input_container_class()
269 ondrop=self.link.callback(move |event| {
270 Msg::Drop(event)
271 })
272 ondragover=self.link.callback(move |event: DragEvent| {
273 Msg::DragOver(event)
274 })
275 ondragexit=self.link.callback(|_| {
276 Msg::DragExit
277 })
278 >
279 <div class="jinya-file-upload__color-container">
280 <div class="jinya-file-upload__container">
281 <label for=id class="jinya-file-upload__label">{&self.label}</label>
282 <input
283 id=id
284 type="file"
285 disabled=self.disabled
286 placeholder=self.placeholder
287 class="jinya-file-upload__input"
288 multiple=self.multiple
289 onchange=self.link.callback(move |value| {
290 let mut result = vec![];
291 if let ChangeData::Files(files) = value {
292 let files = js_sys::try_iter(&files)
293 .unwrap()
294 .unwrap()
295 .into_iter()
296 .map(|v| File::from(v.unwrap()));
297 result.extend(files);
298 }
299 Msg::Files(result)
300 })
301 />
302 <label for=id class="jinya-file-upload__file-info">
303 {if self.filename.is_empty() {
304 &self.placeholder
305 } else {
306 &self.filename
307 }}
308 </label>
309 </div>
310 <label for=id class="jinya-file-upload__button">
311 <span class="mdi mdi-upload"></span>
312 </label>
313 </div>
314 <span class="jinya-file-upload__validation-message">{&self.validation_message}</span>
315 </div>
316 }
317 }
318}