diff --git a/Cargo.lock b/Cargo.lock index 1fd69ab..f2a11cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1153,6 +1153,7 @@ dependencies = [ "clipboard-rs", "eframe", "egui_ltreeview", + "futures", "markdown", "rfd", "serde", @@ -1211,6 +1212,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -1218,6 +1234,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1226,6 +1243,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -1256,6 +1284,12 @@ dependencies = [ "syn", ] +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + [[package]] name = "futures-task" version = "0.3.31" @@ -1268,9 +1302,11 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", + "futures-sink", "futures-task", "memchr", "pin-project-lite", diff --git a/Cargo.toml b/Cargo.toml index 4775d8f..791286c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ tokio = { version = "1.47.1", features = ["full"] } tinyrand = "0.5.0" clipboard-rs = "0.3.0" tempfile = "3.23.0" +futures = "0.3.31" [package.metadata.bundle] identifier = "org.geekfu.fleen" diff --git a/src/fleen_app.rs b/src/fleen_app.rs index dd342c2..bde598a 100644 --- a/src/fleen_app.rs +++ b/src/fleen_app.rs @@ -4,8 +4,10 @@ use std::path::{Path, PathBuf}; use std::process::Command; use clipboard_rs::Clipboard; use clipboard_rs::common::RustImage; +use tempfile::TempDir; use thiserror::Error; use tinyrand::{Rand, Seeded}; +use tokio::task::JoinHandle; use crate::fleen_app::FleenError::{RootDirNonexistence, RootDirPopulated, TargetDir}; use crate::fleen_app::TreeEntry::{CloseDir, Dir}; use crate::renderer; @@ -284,7 +286,7 @@ impl FleenApp { Ok(()) } - pub fn build_and_deploy(&self) -> Result { + pub fn build_and_deploy(&self) -> Result>, FleenError> { let output_dir = tempfile::tempdir().map_err(|_| TargetDir)?; self.build_site(&output_dir.path())?; // Attempt to build the site somewhere @@ -294,10 +296,20 @@ impl FleenApp { } else { let mut command = Command::new(deploy_script_path); command.current_dir(output_dir.path()); // don't consume dir! - match command.output() { + Ok(self.build_and_deploy_inner(output_dir, command)) + } + } + + fn build_and_deploy_inner(&self, output_dir: TempDir, mut command: Command) -> JoinHandle> { + tokio::spawn(async move { + let output = command.output(); + let status = command.status().unwrap(); + output_dir.close().map_err(|e| FleenError::Io(e))?; + + match output { Ok(output) => { let output = String::from_utf8(output.stdout).unwrap_or("Error reading deploy script output".to_string()); - if command.status().unwrap().success() { + if status.success() { Ok(output) } else { Err(FleenError::DeployError(output)) @@ -307,7 +319,7 @@ impl FleenApp { Err(FleenError::DeployError(e.to_string())) } } - } + }) } } diff --git a/src/main.rs b/src/main.rs index c2b2da3..fd3990d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ mod server; mod ui_ext; use std::path::{Path, PathBuf}; +use std::sync::Arc; +use std::task::Waker; use std::time::{Duration, Instant}; use eframe::egui::{Button, Context, Id, RichText}; use eframe::{egui, Frame}; @@ -37,25 +39,27 @@ struct TempMessage { } struct FleenUi { - app: Option, + app: Option>, error: Option, message: Option, selected_file: Option, dialog_mode: Option, server_handle: Option>, server_port: String, - image_message: Option + deploy_handle: Option>>, + image_message: Option, } impl Default for FleenUi { fn default() -> Self { Self { - app: None, + app: None.into(), error: None, message: None, selected_file: None, dialog_mode: None, server_handle: None, + deploy_handle: None, image_message: None, server_port: "3000".to_string(), } @@ -71,7 +75,7 @@ impl FleenUi { if ui.add_fill_width(Button::blue("Open site...")).clicked() && let Some(path) = rfd::FileDialog::new().pick_folder() { match FleenApp::open(path.clone()) { Ok(app) => { - self.app = Some(app); + self.app = Some(app.into()); } Err(err) => { self.error = Some(err) } } @@ -80,7 +84,7 @@ impl FleenUi { if ui.add_fill_width(Button::green("New site...")).clicked() && let Some(path) = rfd::FileDialog::new().pick_folder() { match FleenApp::create(path.clone()) { Ok(app) => { - self.app = Some(app); + self.app = Some(app.into()); self.server_handle = Some(tokio::spawn(start_server(path, 3000))) } Err(err) => { self.error = Some(err) } @@ -105,16 +109,15 @@ impl FleenUi { }); ui.column(width, |ui| self.server_controls(ui)); ui.column(width, |ui| { - if ui.add_fill_width(Button::green("Build and deploy")).clicked() { - match self.app.as_ref().unwrap().build_and_deploy() { - Ok(output) => { self.message = Some(format!("Site built and deployed:\n\n{}", output)) } - Err(err) => { self.error = Some(err) } + ui.add_enabled_ui(self.deploy_handle.is_none(), |ui| { + if ui.add_fill_width(Button::green("Build and deploy")).clicked() { + self.build_and_deploy(); } - } + }); if ui.add_fill_width(Button::blue("Build site...")).clicked() && let Some(path) = rfd::FileDialog::new().pick_folder() { - match self.app.as_ref().unwrap().build_site(&path) { + match self.app.as_ref().as_ref().unwrap().build_site(&path) { Ok(()) => { self.message = Some("Site built successfully".to_string()) } Err(err) => { self.error = Some(err) } } @@ -131,12 +134,19 @@ impl FleenUi { } } + fn build_and_deploy(&mut self) { + match self.app.as_ref().unwrap().build_and_deploy() { + Ok(handle) => { self.deploy_handle = Some(handle) }, + Err(err) => { self.error = Some(err) } + } + } + fn tree_view(&mut self, ui: &mut egui::Ui) { let tv = egui_ltreeview::TreeView::new(Id::from("tree")) .allow_multi_selection(false) .allow_drag_and_drop(false); let (_, actions) = tv.show(ui, |builder| { - for entry in self.app.as_mut().unwrap().file_tree_entries().into_iter() { + for entry in self.app.clone().as_mut().unwrap().file_tree_entries().into_iter() { match entry { TreeEntry::File(p) => builder.leaf(id_for_path(&p), label_for_path(&p)), TreeEntry::Dir(p) => { builder.dir(id_for_path(&p), label_for_path(&p)); }, @@ -351,6 +361,19 @@ impl FleenUi { impl eframe::App for FleenUi { fn update(&mut self, ctx: &Context, _frame: &mut Frame) { + if let Some(handle) = self.deploy_handle.as_ref() { + if handle.is_finished() { + let handle = self.deploy_handle.take().unwrap(); + let result = futures::executor::block_on(async move { handle.await }); + match result { + Err(e) => { self.error = Some(FleenError::DeployError(e.to_string())) } + Ok(Err(e)) => { self.error = Some(e) } + Ok(Ok(s)) => { self.message = Some(s) } + } + } + ctx.request_repaint_after(Duration::from_millis(100)); + } + match &mut self.app { None => self.site_chooser(ctx), Some(_) => self.display(ctx)