|
1 | | -//! A file previewer widget that displays file metadata and previews. |
| 1 | +//! A file previewer modal widget that displays file metadata and previews. |
2 | 2 | //! |
3 | | -//! This widget shows: |
4 | | -//! - File metadata: filename and file size (always displayed) |
5 | | -//! - For images: displays the image preview |
6 | | -//! - For documents: displays only the filename |
7 | | -//! |
8 | | -//! ## Usage Example |
9 | | -//! |
10 | | -//! ```rust,ignore |
11 | | -//! // Set the file metadata and file data |
12 | | -//! file_previewer.set_content(cx, FileData(file_metadata, file_data)); // 500 KB file |
| 3 | +//! This widget handles FilePreviewerAction to show and hide the previewer modal. |
| 4 | +//! It also emits FilePreviewerAction::Upload action to upload the selected file. |
13 | 5 | //! ``` |
14 | 6 |
|
| 7 | +use std::sync::Arc; |
| 8 | + |
15 | 9 | use makepad_widgets::*; |
16 | 10 | use makepad_widgets::image_cache::{ImageBuffer, ImageError}; |
17 | 11 | use mime_guess::{Mime, mime}; |
@@ -41,81 +35,74 @@ live_design! { |
41 | 35 | use crate::shared::styles::*; |
42 | 36 | use crate::shared::icon_button::RobrixIconButton; |
43 | 37 |
|
44 | | - FilePreviewerButton = <RobrixIconButton> { |
45 | | - width: 44, height: 44 |
46 | | - align: {x: 0.5, y: 0.5}, |
47 | | - spacing: 0, |
48 | | - padding: 0, |
49 | | - draw_bg: { |
50 | | - color: (COLOR_SECONDARY * 0.925) |
51 | | - } |
52 | | - draw_icon: { |
53 | | - svg_file: (ICON_ZOOM_OUT), |
54 | | - fn get_color(self) -> vec4 { |
55 | | - return #x0; |
56 | | - } |
57 | | - } |
58 | | - icon_walk: {width: 27, height: 27} |
59 | | - } |
60 | | - |
61 | 38 | pub FilePreviewer = {{FilePreviewer}} { |
62 | 39 | width: Fit, height: Fit |
63 | 40 | wrapper = <RoundedView> { |
64 | 41 | width: Fit, height: Fit |
65 | 42 | align: {x: 0.5} |
66 | 43 | flow: Down |
67 | 44 | padding: {top: 20, right: 20, bottom: 10, left: 20} |
68 | | - |
69 | 45 | show_bg: true |
70 | 46 | draw_bg: { |
71 | 47 | color: (COLOR_PRIMARY) |
72 | 48 | border_radius: 4 |
73 | 49 | } |
74 | 50 |
|
75 | | - header_view = <View> { |
76 | | - width: 250 |
77 | | - height: Fit |
78 | | - flow: Right |
79 | | - align: {x: 1.0, y: 0.0} |
80 | | - margin: {top: -15, right: -25, bottom: 10} |
81 | | - |
82 | | - close_button = <FilePreviewerButton> { |
83 | | - draw_icon: { svg_file: (ICON_CLOSE) } |
84 | | - icon_walk: {width: 21, height: 21 } |
85 | | - } |
86 | | - } |
87 | 51 | <View> { |
88 | 52 | width: Fit, height: Fit |
89 | | - flow: Right |
90 | | - // Document view (visible only for non-image files) |
91 | | - document_view = <View> { |
92 | | - visible: false, |
93 | | - width: Fit |
94 | | - height: 40 |
95 | | - flow: Down |
96 | | - align: {x: 0.5, y: 0.5} |
97 | | - file_icon = <Icon> { |
98 | | - draw_icon: { |
99 | | - svg_file: (ICON_FILE), |
100 | | - color: #999, |
| 53 | + flow: Down |
| 54 | + |
| 55 | + <View> { |
| 56 | + width: Fit, height: Fit |
| 57 | + |
| 58 | + header_label = <Label> { |
| 59 | + width: 200 |
| 60 | + height: 50 |
| 61 | + padding: 0 |
| 62 | + //margin: {bottom: 15} |
| 63 | + draw_text: { |
| 64 | + text_style: <REGULAR_TEXT>{font_size: 15}, |
| 65 | + color: (COLOR_TEXT), |
| 66 | + wrap: Word |
101 | 67 | } |
102 | | - icon_walk: { width: 20, height: 20 } |
| 68 | + text: "Upload Files" |
103 | 69 | } |
104 | 70 | } |
105 | | - // File metadata section (always visible) |
106 | | - metadata_view = <View> { |
107 | | - width: 250 |
108 | | - height: Fit |
109 | | - flow: Down |
110 | | - spacing: 5 |
111 | | - |
112 | | - filename_text = <Label> { |
113 | | - width: Fill |
| 71 | + <View> { |
| 72 | + width: Fit, height: Fit |
| 73 | + flow: Right |
| 74 | + |
| 75 | + // Document view (visible only for non-image files) |
| 76 | + document_view = <View> { |
| 77 | + visible: false, |
| 78 | + width: Fit |
| 79 | + height: 40 |
| 80 | + flow: Down |
| 81 | + align: {x: 0.5, y: 0.5} |
| 82 | + file_icon = <Icon> { |
| 83 | + draw_icon: { |
| 84 | + svg_file: (ICON_FILE), |
| 85 | + color: #999, |
| 86 | + } |
| 87 | + icon_walk: { width: 20, height: 20 } |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + // File metadata section (always visible) |
| 92 | + metadata_view = <View> { |
| 93 | + width: 250 |
114 | 94 | height: Fit |
115 | | - draw_text: { |
116 | | - text_style: <REGULAR_TEXT>{font_size: 14}, |
117 | | - color: #000, |
118 | | - wrap: Word |
| 95 | + flow: Down |
| 96 | + spacing: 5 |
| 97 | + |
| 98 | + filename_text = <Label> { |
| 99 | + width: Fill |
| 100 | + height: Fit |
| 101 | + draw_text: { |
| 102 | + text_style: <REGULAR_TEXT>{font_size: 14}, |
| 103 | + color: #000, |
| 104 | + wrap: Word |
| 105 | + } |
119 | 106 | } |
120 | 107 | } |
121 | 108 | } |
@@ -186,39 +173,40 @@ pub enum FilePreviewerAction { |
186 | 173 | /// Display the FilePreviewer widget with the given file data. |
187 | 174 | Show(FileData), |
188 | 175 | /// Upload the file with the given data. |
189 | | - Upload(FileData), |
| 176 | + Upload(FilePreviewerMetaData), |
190 | 177 | /// Hide the FilePreviewer widget. |
191 | 178 | Hide, |
192 | 179 | /// No action. |
193 | 180 | None, |
194 | 181 | } |
195 | 182 |
|
196 | 183 | /// Type alias for file data message sent through the channel. |
197 | | -pub type FileData = (FilePreviewerMetaData, Vec<u8>); |
| 184 | +pub type FileData = Arc<(FilePreviewerMetaData, Option<Vec<u8>>)>; |
198 | 185 |
|
199 | 186 | /// Type alias for the receiver that gets file data. |
200 | | -pub type FileLoadReceiver = std::sync::mpsc::Receiver<FileData>; |
| 187 | +pub type FileLoadReceiver = std::sync::mpsc::Receiver<Option<FileData>>; |
201 | 188 |
|
202 | 189 | /// A widget that previews files by displaying metadata and content based on file type. |
203 | 190 | #[derive(Live, Widget, LiveHook)] |
204 | 191 | pub struct FilePreviewer { |
205 | 192 | #[redraw] #[deref] view: View, |
206 | 193 | #[rust] file_type: FileType, |
207 | | - #[rust] file_data: Option<FileData>, |
| 194 | + #[rust] file_meta: Option<FilePreviewerMetaData>, |
208 | 195 | } |
| 196 | + |
209 | 197 | impl FilePreviewer { |
210 | 198 | /// Sets the file content to preview, including metadata and image/document display. |
211 | 199 | /// For images, attempts to decode and display the preview. Falls back to document view on error. |
212 | 200 | fn set_content(&mut self, cx: &mut Cx, file_load_message: FileData) { |
213 | | - let (file_metadata, file_data) = file_load_message; |
214 | | - self.set_metadata(cx, &file_metadata.filename, file_metadata.file_size as u64); |
215 | | - |
216 | | - let close_button = self.view.button(ids!(wrapper.header_view.close_button)); |
217 | | - close_button.reset_hover(cx); |
218 | | - close_button.set_enabled(cx, true); |
219 | | - |
| 201 | + let (file_metadata, file_data) = file_load_message.as_ref(); |
| 202 | + self.file_meta = Some(file_metadata.clone()); |
| 203 | + self.set_metadata(cx, &file_metadata.filename, file_metadata.file_size); |
| 204 | + |
220 | 205 | if file_metadata.mime.type_() == mime::IMAGE { |
221 | 206 | // Attempt to decode the image data for preview |
| 207 | + let Some(file_data) = file_data else { |
| 208 | + return; |
| 209 | + }; |
222 | 210 | if let Ok(image_buffer) = load_image_from_bytes(&file_data) { |
223 | 211 | // Get image dimensions to calculate aspect-ratio preserving size |
224 | 212 | let (image_width, image_height) = (image_buffer.width, image_buffer.height); |
@@ -271,50 +259,24 @@ impl Widget for FilePreviewer { |
271 | 259 |
|
272 | 260 | impl MatchEvent for FilePreviewer { |
273 | 261 | fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) { |
274 | | - if self.view.button(ids!(wrapper.header_view.close_button)).clicked(actions) { |
275 | | - cx.action(FilePreviewerAction::Hide); |
276 | | - return; |
277 | | - } |
278 | | - |
279 | | - if self.view.button(ids!(close_modal_button)).clicked(actions) { |
280 | | - cx.action(FilePreviewerAction::Hide); |
281 | | - return; |
282 | | - } |
283 | 262 |
|
284 | 263 | if self.view.button(ids!(wrapper.buttons_view.cancel_button)).clicked(actions) { |
285 | 264 | cx.action(FilePreviewerAction::Hide); |
286 | | - self.view.button(ids!(wrapper.buttons_view.cancel_button)).set_enabled(cx, false); |
| 265 | + self.file_meta = None; |
287 | 266 | return; |
288 | 267 | } |
289 | 268 |
|
290 | 269 | if self.view.button(ids!(wrapper.buttons_view.upload_button)).clicked(actions) { |
291 | | - if let Some(file_data) = self.file_data.take() { |
292 | | - cx.action(FilePreviewerAction::Upload(file_data)); |
293 | | - cx.action(FilePreviewerAction::Hide); |
294 | | - self.view.button(ids!(wrapper.buttons_view.upload_button)).set_enabled(cx, false); |
| 270 | + if let Some(file_meta) = self.file_meta.take() { |
| 271 | + cx.action(FilePreviewerAction::Upload(file_meta)); |
295 | 272 | } |
296 | 273 | return; |
297 | 274 | } |
298 | 275 |
|
299 | 276 | for action in actions { |
300 | 277 | if let Some(FilePreviewerAction::Show(file_data)) = action.downcast_ref() { |
301 | | - // Reset all button states when showing the previewer |
302 | | - let close_button = self.view.button(ids!(wrapper.header_view.close_button)); |
303 | | - close_button.reset_hover(cx); |
304 | | - close_button.set_enabled(cx, true); |
305 | | - |
306 | | - let cancel_button = self.view.button(ids!(wrapper.buttons_view.cancel_button)); |
307 | | - cancel_button.reset_hover(cx); |
308 | | - cancel_button.set_enabled(cx, true); |
309 | | - |
310 | | - let upload_button = self.view.button(ids!(wrapper.buttons_view.upload_button)); |
311 | | - upload_button.reset_hover(cx); |
312 | | - upload_button.set_enabled(cx, true); |
313 | | - |
314 | | - // Store the file data for later upload, clone only once |
315 | | - let cloned_file_data = file_data.clone(); |
316 | | - self.set_content(cx, cloned_file_data.clone()); |
317 | | - self.file_data = Some(cloned_file_data); |
| 278 | + self.set_content(cx, file_data.clone()); |
| 279 | + self.file_meta = Some(file_data.0.clone()); |
318 | 280 | continue; |
319 | 281 | } |
320 | 282 | } |
@@ -413,7 +375,7 @@ pub enum FileType { |
413 | 375 | /// |
414 | 376 | /// Uses binary units (1024 bytes = 1 KB) for conversion. |
415 | 377 | /// For sizes less than 1 KB, displays the exact byte count without decimal places. |
416 | | -fn format_file_size(bytes: u64) -> String { |
| 378 | +pub fn format_file_size(bytes: u64) -> String { |
417 | 379 | const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"]; |
418 | 380 |
|
419 | 381 | if bytes == 0 { |
@@ -442,5 +404,7 @@ pub struct FilePreviewerMetaData { |
442 | 404 | pub filename: String, |
443 | 405 | pub mime: Mime, |
444 | 406 | /// File size in bytes |
445 | | - pub file_size: usize, |
| 407 | + pub file_size: u64, |
| 408 | + /// Path to the original file |
| 409 | + pub file_path: std::path::PathBuf, |
446 | 410 | } |
0 commit comments