kayrx_ui/widget/
file_upload.rs1use web_sys::{DragEvent, HtmlInputElement, MouseEvent, MouseEventInit};
2use crate::fabric::prelude::*;
3use crate::fabric::services::reader::File;
4
5pub struct FileUpload {
6 props: Props,
7 link: ComponentLink<Self>,
8 input_ref: NodeRef,
9 file_name: String,
10}
11
12pub enum Msg {
13 Files(Vec<File>),
14 Click,
15 Clear,
16 Nop,
17}
18
19#[derive(Clone, Properties)]
20pub struct Props {
21 #[prop_or_default]
22 pub disabled: bool,
23 #[prop_or_default]
24 pub multiple: bool,
25 #[prop_or_else(Callback::noop)]
26 pub onchange: Callback<Vec<File>>,
27}
28
29impl Component for FileUpload {
30 type Message = Msg;
31 type Properties = Props;
32
33 fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
34 FileUpload {
35 props,
36 link,
37 input_ref: NodeRef::default(),
38 file_name: String::new(),
39 }
40 }
41
42 fn update(&mut self, msg: Self::Message) -> ShouldRender {
43 match msg {
44 Msg::Files(v) => {
45 if let Some(f) = v.first() {
46 self.file_name = f.name();
47 }
48 self.props.onchange.emit(v);
49 return true;
50 }
51 Msg::Click => {
52 self.open_file_dialog();
53 }
54 Msg::Clear => {
55 self.file_name = String::new();
56 self.props.onchange.emit(Vec::new());
57 return true;
58 }
59 Msg::Nop => {}
60 }
61 false
62 }
63
64 fn change(&mut self, props: Self::Properties) -> ShouldRender {
65 self.props = props;
66 self.file_name = String::new();
67 true
68 }
69
70 fn view(&self) -> Html {
71 let ondragover = self.link.callback(|e: DragEvent| {
72 e.prevent_default();
73 Msg::Nop
74 });
75 let ondrop = self.link.callback(|e: DragEvent| {
76 e.prevent_default();
77 if let Some(ft) = e.data_transfer() {
78 return Msg::Files(
79 js_sys::try_iter(&ft.files().unwrap())
80 .unwrap()
81 .unwrap()
82 .map(|v| File::from(v.unwrap()))
83 .collect(),
84 );
85 }
86
87 Msg::Nop
88 });
89 let onchange = self.link.callback(|e| {
90 let res = match e {
91 ChangeData::Files(f) => js_sys::try_iter(&f)
92 .unwrap()
93 .unwrap()
94 .map(|v| File::from(v.unwrap()))
95 .collect(),
96 _ => unreachable!(),
97 };
98 Msg::Files(res)
99 });
100 html! {
101 <div class="bow-file-upload"
102 ondrop=ondrop
103 ondragover=ondragover
104 disabled=self.props.disabled onclick=self.link.callback(|_| Msg::Click)>
105
106 <input type="file" hidden=true
107 ref=self.input_ref.clone(),
108 multiple=self.props.multiple
109 onchange=onchange></input>
110
111 { self.render_icon() }
112 { self.render_caption() }
113 </div>
114 }
115 }
116}
117
118impl FileUpload {
119 fn open_file_dialog(&self) {
120 if let Some(el) = self.input_ref.cast::<HtmlInputElement>() {
121 let mut dict = MouseEventInit::new();
122 dict.bubbles(false);
123 dict.cancelable(false);
124 el.dispatch_event(&MouseEvent::new_with_mouse_event_init_dict("click", &dict).unwrap())
125 .unwrap();
126 return;
127 }
128
129 unreachable!()
130 }
131
132 fn render_icon(&self) -> Html {
133 html! {
134 <svg class="bow-file-upload__icon" width="52" height="32" viewBox="0 0 52 32" fill="none" xmlns="http://www.w3.org/2000/svg">
135 <path d="M42.1345 12.4828C41.4785 12.4828 40.8238 12.5473 40.1809 12.676C39.6961 10.2827 38.1288 8.2411 35.9282 7.13606C33.7281 6.03103 31.1369 5.98468 28.8974 7.01001C26.8707 1.48786 20.7021 -1.36331 15.1194 0.641434C9.53679 2.64618 6.65439 8.74835 8.68109 14.2701C3.79081 14.423 -0.0721116 18.4268 0.00102147 23.2658C0.0745925 28.1053 4.0575 31.9922 8.95042 32H42.1345C47.5827 32 52 27.631 52 22.2414C52 16.8518 47.5827 12.4828 42.1345 12.4828V12.4828Z" fill="#3182CE"/>
136 </svg>
137 }
138 }
139
140 fn render_caption(&self) -> Html {
141 html! {
142 <>
143 <span class="bow-file-upload__caption">
144 {
145 if self.file_name.len() == 0 {
146 html!{
147 <>
148 {"Drop file"}{if self.props.multiple {"s"} else {""}}
149 {" here"}<br/>{"or click to select"}
150 </>
151 }
152 } else { html!{&self.file_name}}
153 }</span>
154 { if self.file_name.len() > 0 {self.render_clear_button() } else { html!{} }}
155 </>
156 }
157 }
158 fn render_clear_button(&self) -> Html {
159 html! {
160 <div class="bow-file-upload__reset-button" onclick=self.link.callback(|_|Msg::Clear)>
161 <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
162 <rect y="0.666626" width="0.942809" height="16.0278" transform="rotate(-45 0 0.666626)" fill="white"/>
163 <rect width="0.942809" height="16.0278" transform="matrix(-0.707107 -0.707107 -0.707107 0.707107 12 0.666626)" fill="white"/>
164 </svg>
165 </div>
166 }
167 }
168}