1use eframe::egui;
4use poll_promise::Promise;
5use std::{fmt::Debug, fs::read, path::PathBuf};
6
7use crate::errors::AppError;
8
9#[derive(Default, Clone)]
11pub struct File {
12 pub data: Vec<u8>,
14 pub path: PathBuf,
16}
17
18#[derive(Default, serde::Deserialize, serde::Serialize)]
20pub struct FileHandler {
21 #[serde(skip)]
23 pub dropped_files: Vec<egui::DroppedFile>,
24
25 #[serde(skip)]
27 pub file_upload: Option<Promise<Result<FileState, AppError>>>,
28}
29
30#[derive(Clone)]
32pub enum FileState {
33 NotSelected,
35 UploadedOrSelected,
37 NoUpload,
39 Ready(File),
41}
42
43impl Debug for FileHandler {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 let mut debug_fmt = f.debug_struct("FileHandler");
46 debug_fmt.field("dropped_files", &self.dropped_files);
47 if self.file_upload.is_some() {
48 let val = String::new();
49 debug_fmt.field("file_upload", &val);
50 }
51 debug_fmt.finish()
52 }
53}
54
55impl FileHandler {
56 #[cfg(target_arch = "wasm32")]
58 pub fn handle_file_open(&mut self) {
59 self.file_upload = Some(Promise::spawn_local(async {
60 log::info!("rfd start");
61 let file_selected = rfd::AsyncFileDialog::new().pick_file().await;
62 log::info!("rfd result {:?}", file_selected);
63 if let Some(curr_file) = file_selected {
64 let buf = curr_file.read().await;
65 return Ok(FileState::Ready(File {
66 data: buf,
67 path: PathBuf::from(curr_file.file_name()),
68 }));
69 }
70 Ok(FileState::NotSelected)
72 }));
73 }
74
75 #[cfg(not(target_arch = "wasm32"))]
77 pub fn handle_file_open(&mut self) {
78 self.file_upload = Some(Promise::spawn_thread("slow", move || {
79 if let Some(path_buf) = rfd::FileDialog::new().pick_file() {
80 if let Some(path) = path_buf.to_str() {
82 let buf = std::fs::read(path);
83 let buf = match buf {
84 Ok(v) => v,
85 Err(e) => {
86 log::warn!("{e:?}");
87 return Err(AppError::new(e.to_string()));
88 }
89 };
90 return Ok(FileState::Ready(File {
91 data: buf,
92 path: path_buf,
93 }));
94 }
95 return Err(AppError::new("Invalid file path".to_string()));
96 }
97 Ok(FileState::NotSelected)
99 }));
100 }
101
102 pub fn reset(&mut self) {
104 self.file_upload = None;
105 }
106
107 fn handle_file_upload(&mut self) -> Result<FileState, AppError> {
109 match &self.file_upload {
110 Some(result) => match result.ready() {
111 Some(Ok(state)) => Ok(state.clone()),
112 Some(Err(e)) => Err(e.clone()),
113 None => Ok(FileState::UploadedOrSelected), },
115 None => Ok(FileState::NoUpload), }
117 }
118
119 fn handle_file_dropped(&mut self) -> Result<Option<File>, AppError> {
121 if self.dropped_files.is_empty() {
122 return Ok(None);
123 }
124 let file = self.dropped_files.remove(0);
125 if cfg!(not(target_arch = "wasm32")) {
126 if let Some(path) = file.path.as_deref() {
127 let file = read(path)?;
128 return Ok(Some(File {
129 data: file,
130 path: path.to_path_buf(),
131 }));
132 }
133 } else if cfg!(target_arch = "wasm32")
134 && let Some(bytes) = file.bytes.as_deref()
135 {
136 return Ok(Some(File {
137 data: bytes.to_vec(),
138 path: file.path.unwrap_or(PathBuf::from(file.name)),
139 }));
140 }
141 Ok(None)
142 }
143
144 pub fn handle_files(&mut self, ctx: &egui::Context) -> Result<Option<File>, AppError> {
148 ctx.input(|i| {
149 if !i.raw.dropped_files.is_empty() {
150 self.dropped_files.clone_from(&i.raw.dropped_files);
152 }
153 });
154 match self.handle_file_upload() {
155 Ok(state) => match state {
156 FileState::NotSelected => {
157 log::info!("No file selected");
158 self.reset();
159 }
160 FileState::UploadedOrSelected => {
161 log::info!("File is being uploaded or selected...");
162 return Ok(None);
163 }
164 FileState::Ready(data) => {
165 log::info!("File uploaded successfully");
166 self.reset();
167 return Ok(Some(data));
168 }
169 FileState::NoUpload => {
170 self.reset();
171 }
172 },
173 Err(e) => {
174 self.reset();
175 return Err(e);
176 }
177 }
178 if let Some(file_dropped) = self.handle_file_dropped()? {
179 return Ok(Some(file_dropped));
180 }
181 Ok(None)
182 }
183}