dxc_components/image_viewer/
image_viewer.rs

1use super::action::Action;
2use super::props::ImageViewerProps;
3use super::transform::Transform;
4use crate::focus_trap::DxcFocusTrap;
5use crate::icon::DxcIcon;
6use crate::teleport::DxcTeleport;
7use crate::transition::DxcTransition;
8use dioxus::prelude::*;
9use dxc_hooks::UseNamespace;
10use dxc_icons::{
11    ArrowLeft, ArrowRight, Close, FullScreen, RefreshLeft, RefreshRight, ScaleToOriginal, ZoomIn,
12    ZoomOut,
13};
14use dxc_types::namespace::Block;
15use dxc_macros::classes;
16
17#[component]
18pub fn DxcImageViewer(props: ImageViewerProps) -> Element {
19    let mut active_index = use_signal(|| props.initial_index());
20    let current_index = use_memo(move || active_index());
21    let crossorigin = use_signal(|| props.crossorigin());
22    let mut loading = use_signal(|| true);
23    let hide_on_click_modal = use_signal(|| props.hide_on_click_modal());
24    let infinite = use_signal(|| props.infinite());
25    let transform = use_signal(|| Transform::default());
26
27    let zoom_rate = use_signal(|| props.zoom_rate());
28    let min_scale = use_signal(|| props.min_scale());
29    let max_scale = use_signal(|| props.max_scale());
30    let z_index = use_signal(|| props.z_index());
31
32    let url_list = use_signal(|| props.url_list());
33    let is_single = use_signal(|| {
34        return url_list().iter().len() <= 1;
35    });
36
37    let is_first = use_signal(|| active_index() == 0);
38    let is_last = use_signal(|| active_index() == url_list().len().saturating_sub(1));
39
40    let show_progress = use_signal(|| props.show_progress());
41    let progress = use_memo(move || format!("{} / {}", active_index() + 1, url_list.len()));
42
43    // style
44    let ns = UseNamespace::new(Block::ImageViewer, None);
45
46    let arrow_pre_classes = classes! {
47        ns.e_(String::from("btn")),
48        ns.e_(String::from("prev")),
49        ns.is_(String::from("disabled"), Some(!infinite() && is_first())),
50    };
51    let arrow_next_classes = classes! {
52        ns.e_(String::from("btn")),
53        ns.e_(String::from("next")),
54        ns.is_(String::from("disabled"), Some(!infinite() && is_last())),
55    };
56    let img_style = use_memo(move || {
57        let t = transform();
58        let scale = t.scale;
59        let deg = t.deg;
60        let enable_transition = t.enable_transition;
61        let translate_x = t.offset_x / scale;
62        let translate_y = t.offset_y / scale;
63
64        if scale == 0.0 {
65            return "transform: scale(0);".to_string();
66        }
67
68        let radian = (deg * std::f64::consts::PI) / 180.0;
69        let cos_radian = radian.cos();
70        let sin_radian = radian.sin();
71
72        let new_translate_x = translate_x * cos_radian + translate_y * sin_radian;
73        let new_translate_y = translate_y * cos_radian - translate_x * sin_radian;
74
75        let style = format!(
76            "transform: scale({scale}) rotate({deg}deg) translate({new_translate_x}px, {new_translate_y}px); transition: {}; max-width: 100%; max-height: 100%;",
77            if enable_transition {
78                "transform 0.3s"
79            } else {
80                "transform 0.0s"
81            }
82        );
83
84        style
85    });
86
87    // event
88    // let regisiter_event_listener = move || {
89    //     let key_down_handler = move |evt: Event<KeyboardData>| match evt.key() {
90    //         Key::Escape => props.close_on_press_escape.unwrap_or(false),
91    //         _ => false,
92    //     };
93    // };
94
95    let mut set_active_item = move |index: usize| {
96        let len = url_list().len();
97
98        if len == 0 {
99            return;
100        }
101
102        active_index.set((index + len) % len);
103    };
104
105    let mut prev = move || {
106        let len = url_list().len();
107
108        if is_first() && !infinite() {
109            return;
110        }
111
112        set_active_item(active_index().checked_sub(1).unwrap_or(len - 1));
113    };
114
115    let mut next = move || {
116        let len = url_list().len();
117
118        if is_last() && !infinite() {
119            return;
120        }
121
122        set_active_item(active_index().checked_add(1).unwrap_or(len + 1));
123    };
124
125    let hide = move |evt: Event<MouseData>| {
126        evt.stop_propagation();
127        if let Some(onclose_handler) = props.onclose {
128            onclose_handler.call(());
129        }
130    };
131
132    let mut handle_actions = {
133        let mut transform = transform.clone();
134
135        move |action: Action| {
136            if loading() {
137                return;
138            }
139            let mut t = transform.write();
140
141            let rotate_deg = 90.0;
142            let enable_transition = true;
143
144            match action {
145                Action::ZoomIn => {
146                    if t.scale > min_scale() {
147                        t.scale *= zoom_rate();
148                    }
149                    println!("Action: {:?}", action);
150                }
151                Action::ZoomOut => {
152                    if t.scale < max_scale() {
153                        t.scale /= zoom_rate();
154                    }
155                }
156                Action::Clockwise => {
157                    t.deg += rotate_deg;
158                }
159                Action::Anticlockwise => {
160                    t.deg -= rotate_deg;
161                }
162            }
163
164            t.enable_transition = enable_transition;
165        }
166    };
167
168    let mut toggle_mode_icon = use_signal(|| false);
169
170    let mut toogle_mode = move || {
171        if loading() {
172            return;
173        }
174        toggle_mode_icon.set(!toggle_mode_icon());
175    };
176
177    let slot_progress = props.progress.clone();
178    let slot_toolbar = props.toolbar.clone();
179
180    rsx! {
181        DxcTeleport {
182            DxcTransition {
183                div {
184                    tabindex: "-1",
185                    z_index: z_index(),
186                    class: ns.e_(String::from("wrapper")),
187                    DxcFocusTrap{
188                        loop_: true,
189                        trapped: true,
190                        div {
191                            class: ns.e_(String::from("mask")),
192                            onclick: move |evt| {
193                                if hide_on_click_modal() {
194                                    hide(evt)
195                                }
196                            }
197                        }
198
199                        //close
200                        span {
201                            class: format!("{} {}",ns.e_(String::from("btn")),ns.e_(String::from("close"))),
202                            onclick: move |evt| {
203                                hide(evt)
204                            },
205                            DxcIcon { Close { } }
206                        }
207
208                        // arrow
209                        if !is_single() {
210                            span {
211                                class: arrow_pre_classes,
212                                onclick: move |_| { prev() },
213                                DxcIcon { ArrowLeft { } }
214                            }
215                            span {
216                                class: arrow_next_classes,
217                                onclick: move |_| { next() },
218                                DxcIcon { ArrowRight {  } }
219                            }
220                        }
221
222                        if slot_progress.is_some() || show_progress(){
223                            div {
224                                class: format!("{} {}", ns.e_(String::from("btn")), ns.e_(String::from("progress"))),
225                                {slot_progress}
226                                {progress()}
227                            }
228                        }
229
230                        // actions
231                        div {
232                            class: format!("{} {}", ns.e_(String::from("btn")), ns.e_(String::from("actions"))),
233
234                            div {
235                                class: ns.e_(String::from("actions__inner")),
236                                {slot_toolbar}
237
238                                DxcIcon {
239                                    onclick: move |_| handle_actions(Action::ZoomOut),
240                                    ZoomOut {  }
241                                }
242                                DxcIcon {
243                                    onclick: move |_| handle_actions(Action::ZoomIn),
244                                    ZoomIn {  }
245                                }
246                                i { class:ns.e_(String::from("actions__divider")) }
247                                DxcIcon {
248                                    onclick: move |_| toogle_mode(),
249                                    if toggle_mode_icon() {
250                                        ScaleToOriginal {  }
251                                    } else {
252                                        FullScreen {  }
253                                    }
254                                }
255                                i { class:ns.e_(String::from("actions__divider")) }
256                                DxcIcon {
257                                    onclick: move |_| handle_actions(Action::Anticlockwise),
258                                    RefreshLeft {  }
259                                }
260                                DxcIcon {
261                                    onclick: move |_| handle_actions(Action::Clockwise),
262                                    RefreshRight {  }
263                                }
264                            }
265                        }
266
267                        // canvas
268                        div {
269                            class:ns.e_(String::from("canvas")),
270                            for (index, item) in url_list().iter().enumerate() {
271                                if index == current_index() {
272                                    img {
273                                        key: {index},
274                                        src: "{item}",
275                                        class: ns.e_(String::from("img")),
276                                        style: img_style,
277                                        crossorigin: crossorigin(),
278                                        onload: move |_| {
279                                            loading.set(false);
280                                        },
281                                        onerror: move |_| {
282                                            loading.set(false);
283                                        },
284                                        onmousedown: move |evt| {
285                                            evt.prevent_default();
286                                        },
287                                    }
288                                }
289                            }
290                        }
291                    }
292                }
293            }
294        }
295    }
296}