dxc_components/image_viewer/
image_viewer.rs1use 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 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 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 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 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 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 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}