use eframe::egui;
use poll_promise::Promise;
use std::{fmt::Debug, fs::read, path::PathBuf};
use crate::errors::AppError;
#[derive(Default, Clone)]
pub struct File {
pub data: Vec<u8>,
pub path: PathBuf,
}
#[derive(Default, serde::Deserialize, serde::Serialize)]
pub struct FileHandler {
#[serde(skip)]
pub dropped_files: Vec<egui::DroppedFile>,
#[serde(skip)]
pub file_upload: Option<Promise<Result<FileState, AppError>>>,
}
#[derive(Clone)]
pub enum FileState {
NotSelected,
UploadedOrSelected,
NoUpload,
Ready(File),
}
impl Debug for FileHandler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug_fmt = f.debug_struct("FileHandler");
debug_fmt.field("dropped_files", &self.dropped_files);
if self.file_upload.is_some() {
let val = String::new();
debug_fmt.field("file_upload", &val);
}
debug_fmt.finish()
}
}
impl FileHandler {
#[cfg(target_arch = "wasm32")]
pub fn handle_file_open(&mut self) {
self.file_upload = Some(Promise::spawn_local(async {
log::info!("rfd start");
let file_selected = rfd::AsyncFileDialog::new().pick_file().await;
log::info!("rfd result {:?}", file_selected);
if let Some(curr_file) = file_selected {
let buf = curr_file.read().await;
return Ok(FileState::Ready(File {
data: buf,
path: PathBuf::from(curr_file.file_name()),
}));
}
Ok(FileState::NotSelected)
}));
}
#[cfg(not(target_arch = "wasm32"))]
pub fn handle_file_open(&mut self) {
self.file_upload = Some(Promise::spawn_thread("slow", move || {
if let Some(path_buf) = rfd::FileDialog::new().pick_file() {
if let Some(path) = path_buf.to_str() {
let buf = std::fs::read(path);
let buf = match buf {
Ok(v) => v,
Err(e) => {
log::warn!("{e:?}");
return Err(AppError::new(e.to_string()));
}
};
return Ok(FileState::Ready(File {
data: buf,
path: path_buf,
}));
}
return Err(AppError::new("Invalid file path".to_string()));
}
Ok(FileState::NotSelected)
}));
}
pub fn reset(&mut self) {
self.file_upload = None;
}
fn handle_file_dropped(&mut self) -> Result<Option<File>, AppError> {
if self.dropped_files.is_empty() {
return Ok(None);
}
let file = self.dropped_files.remove(0);
if cfg!(not(target_arch = "wasm32")) {
if let Some(path) = file.path.as_deref() {
let file = read(path)?;
return Ok(Some(File {
data: file,
path: path.to_path_buf(),
}));
}
} else if cfg!(target_arch = "wasm32")
&& let Some(bytes) = file.bytes.as_deref()
{
return Ok(Some(File {
data: bytes.to_vec(),
path: file.path.unwrap_or(PathBuf::from(file.name)),
}));
}
Ok(None)
}
pub fn handle_files(&mut self, ctx: &egui::Context) -> Result<Option<File>, AppError> {
ctx.input(|i| {
if !i.raw.dropped_files.is_empty() {
self.dropped_files.clone_from(&i.raw.dropped_files);
}
});
let file_upload_state = match &self.file_upload {
Some(result) => match result.ready() {
Some(Ok(state)) => Ok(state.clone()),
Some(Err(e)) => Err(e.clone()),
None => Ok(FileState::UploadedOrSelected), },
None => Ok(FileState::NoUpload), };
match file_upload_state {
Ok(state) => match state {
FileState::NotSelected => {
log::info!("No file selected");
self.reset();
}
FileState::UploadedOrSelected => {
log::info!("File is being uploaded or selected...");
ctx.request_repaint();
return Ok(None);
}
FileState::Ready(data) => {
log::info!("File uploaded successfully");
self.reset();
return Ok(Some(data));
}
FileState::NoUpload => {
self.reset();
}
},
Err(e) => {
self.reset();
return Err(e);
}
}
if let Some(file_dropped) = self.handle_file_dropped()? {
return Ok(Some(file_dropped));
}
Ok(None)
}
}