Skip to main content

kayrx_ui/widget/
file_upload.rs

1use 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}