This commit is contained in:
2025-08-23 21:39:41 -05:00
parent 251c5a07ce
commit 5b283ae40d
2 changed files with 147 additions and 6 deletions
+46 -2
View File
@@ -1,4 +1,6 @@
use std::cell::RefCell;
use std::fs;
use std::io::Error;
use std::path::PathBuf;
use std::process::Command;
use thiserror::Error;
@@ -12,7 +14,11 @@ pub enum FleenError {
#[error("Root dir is nonempty, you probably don't want to create an app here: {0}")]
RootDirPopulatedError(PathBuf),
#[error("Failed to open {0}: {1}")]
FileOpenError(String, String)
FileIoError(String, String),
#[error("Can't create {0} because it already exists")]
FileExistsError(PathBuf),
#[error("Can't create {0}: {1}")]
FileCreateError(PathBuf, String)
}
#[derive(Clone, Debug)]
@@ -22,6 +28,11 @@ pub enum TreeEntry {
CloseDir
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum FileType {
File, Dir
}
pub struct FleenApp {
root: PathBuf,
files_cache: RefCell<Option<Vec<TreeEntry>>>
@@ -83,8 +94,41 @@ impl FleenApp {
pub fn open_filename(&self, filename: &String) -> Result<(), FleenError> {
Command::new("open").arg(filename.clone()).spawn().map_err(|err| {
FleenError::FileOpenError(filename.clone(), err.to_string())
FleenError::FileIoError(filename.clone(), err.to_string())
})?;
Ok(())
}
pub fn create_page(&self, file_type: FileType, name: &String, parent: Option<&String>) -> Result<(), FleenError> {
let mut target = match parent {
Some(s) => PathBuf::from(s),
None => self.root.clone()
};
target.push(name.clone());
if target.exists() {
return Err(FleenError::FileExistsError(target))
}
match file_type {
FileType::File => std::fs::write(target.clone(), []),
FileType::Dir => std::fs::create_dir(target.clone())
}.map_err(|err| FleenError::FileCreateError(target.clone(), err.to_string()))?;
self.refresh_file_cache(true);
if file_type == FileType::File {
self.open_filename(&target.to_string_lossy().to_string())?
}
Ok(())
}
pub fn delete_page(&self, path: &String) -> Result<(), FleenError> {
let target = PathBuf::from(path);
if target.is_dir() {
fs::remove_dir_all(target)
} else {
fs::remove_file(target)
}.map_err(|err| FleenError::FileIoError(path.clone(), err.to_string()))?;
self.refresh_file_cache(true);
Ok(())
}
}
+101 -4
View File
@@ -4,7 +4,7 @@ use std::path::PathBuf;
use eframe::egui::{Color32, Context, Id, Stroke};
use eframe::{egui, Frame};
use egui_ltreeview::Action;
use crate::fleen_app::{FleenApp, FleenError, TreeEntry};
use crate::fleen_app::{FileType, FleenApp, FleenError, TreeEntry};
fn main() {
let native_options = eframe::NativeOptions::default();
@@ -13,10 +13,17 @@ fn main() {
})).expect("Error running application");
}
enum DialogMode {
NewFile(String),
ConfirmDelete(String),
RenameFile(String)
}
struct FleenUi {
app: Option<FleenApp>,
error: Option<FleenError>,
selected_file: Option<String>
selected_file: Option<String>,
dialog_mode: Option<DialogMode>
}
impl Default for FleenUi {
@@ -24,7 +31,8 @@ impl Default for FleenUi {
Self {
app: None,
error: None,
selected_file: None
selected_file: None,
dialog_mode: None
}
}
}
@@ -55,6 +63,8 @@ impl FleenUi {
}
fn display(&mut self, ctx: &Context) {
let mut new_clicked = false;
egui::CentralPanel::default().show(ctx, |ui| {
ui.horizontal(|ui| {
ui.vertical(|ui| {
@@ -64,10 +74,31 @@ impl FleenUi {
self.handle_error(self.app.as_ref().unwrap().open_filename(fname))
}
}
ui.add(egui::Button::new("New page")).clicked();
if ui.add(egui::Button::new("New page")).clicked() {
new_clicked = true;
self.dialog_mode = Some(DialogMode::NewFile(String::new()));
}
let delete_btn = egui::Button::new("Delete").fill(Color32::DARK_RED);
if let Some(selected) = &self.selected_file {
if ui.add(delete_btn).clicked() {
self.dialog_mode = Some(DialogMode::ConfirmDelete(selected.clone()));
}
} else {
ui.add_enabled(false, delete_btn);
}
});
})
});
match self.dialog_mode {
Some(DialogMode::NewFile(_)) => self.new_file_dialog(ctx, new_clicked),
Some(DialogMode::ConfirmDelete(_)) => self.confirm_delete_dialog(ctx),
Some(DialogMode::RenameFile(_)) => todo!(),
None => {}
}
}
fn tree_view(&mut self, ui: &mut egui::Ui) {
@@ -99,6 +130,72 @@ impl FleenUi {
}
}
fn new_file_dialog(&mut self, ctx: &Context, just_clicked: bool) {
egui::Window::new("New page").collapsible(false).resizable(false).show(ctx, |ui| {
ui.label("Name");
// If we press enter on the text field, it emits lost_focus. We can't just create the file based
// on that, because it's not the only thing that causes it, though. So instead we'll just focus the
// create file button
//let fname = match &mut self.dialog_mode { Some(DialogMode::NewFile(s)) => s, _ => unreachable!() };
let Some(DialogMode::NewFile(fname)) = &mut self.dialog_mode else { unreachable!() };
let name_field = egui::TextEdit::singleline(fname);
let resp = ui.add(name_field);
let enter_key = resp.lost_focus();
// We want to focus the text field if we just opened the dialog, but egui doesn't currently
// offer a way to do that. However, we can pass in a bool for if the button that opens the
// dialog was just clicked, and use that to tell whether we should grab focus. It's only true
// the exact frame that the dialog was opened, which is what we're looking for.
if just_clicked { resp.request_focus() }
ui.horizontal(|ui| {
let mut make_thing = |file_type: FileType| {
let Some(DialogMode::NewFile(fname)) = &self.dialog_mode else { unreachable!() };
let app = self.app.as_ref().unwrap();
let r = app.create_page(file_type,
fname,
self.selected_file.as_ref());
if r.is_err() {
self.handle_error(r);
} else {
self.dialog_mode = None; // Close the dialog, we're done
}
};
let btn = ui.button("New file");
if enter_key { btn.request_focus() }
if btn.clicked() { make_thing(FileType::File) }
if ui.button("New directory").clicked() { make_thing(FileType::Dir) }
if ui.button("Cancel").clicked() {
self.dialog_mode = None
}
})
});
}
fn confirm_delete_dialog(&mut self, ctx: &Context) {
let (mut del, mut cancel) = (false, false);
let Some(DialogMode::ConfirmDelete(fname)) = &self.dialog_mode else { unreachable!() };
egui::Window::new("Are you sure?").collapsible(false).resizable(false).show(ctx, |ui| {
let file = label_for_path(&PathBuf::from(fname));
ui.heading(format!("Really delete {}?", file));
ui.horizontal(|ui| {
let btn = egui::Button::new("Yep, I'm sure").fill(Color32::DARK_RED);
del = ui.add(btn).clicked();
cancel = ui.button("Cancel").clicked();
})
});
if del {
let r = self.app.as_ref().unwrap().delete_page(&fname.clone());
self.dialog_mode = None;
if r.is_err() { self.handle_error(r); }
self.selected_file = None;
} else if cancel {
self.dialog_mode = None
}
}
fn handle_error(&mut self, result: Result<(), FleenError>) {
if let Err(e) = result {
self.error = Some(e)