Compare commits
No commits in common. "009dd4657ff17cf438140822e46a2827f4eb2ca7" and "9e763a91eff35cd8d3320856ebc0e443af6dbff6" have entirely different histories.
009dd4657f
...
9e763a91ef
2644
Cargo.lock
generated
2644
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
13
Cargo.toml
@ -4,13 +4,18 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["wayland", "x11"]
|
||||
wayland = ["eframe/wayland"]
|
||||
x11 = ["eframe/x11"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
color-eyre = "0.6.3"
|
||||
eframe = { version = "0.28.1", features = [
|
||||
"wgpu",
|
||||
"default_fonts",
|
||||
], default-features = false }
|
||||
egui_extras = "0.28.1"
|
||||
env_logger = { version = "0.11", features = ["auto-color", "humantime"] }
|
||||
posix-acl = { git = "https://git.hibas.dev/hibas123/PosixACL", tag = "0.1.6" }
|
||||
ratatui = { version = "0.29.0", features = ["all-widgets"] }
|
||||
posix-acl = { git = "https://git.hibas.dev/hibas123/PosixACL", tag = "0.1.5" }
|
||||
walkdir = "2"
|
||||
|
25
src/main.rs
25
src/main.rs
@ -1,21 +1,22 @@
|
||||
use anyhow::Result;
|
||||
use ratatui::{
|
||||
crossterm::event::{self, Event},
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
use eframe::egui;
|
||||
|
||||
mod helper;
|
||||
mod ui;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]),
|
||||
hardware_acceleration: eframe::HardwareAcceleration::Preferred,
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
"ACL Editor",
|
||||
options,
|
||||
Box::new(|cc| Ok(Box::new(ui::App::new(&cc.egui_ctx)))),
|
||||
)
|
||||
.expect("Failed to run eframe app");
|
||||
|
||||
color_eyre::install().expect("failed to install color_eyre");
|
||||
|
||||
let mut terminal = ratatui::init();
|
||||
let result = ui::App::default().run(&mut terminal);
|
||||
ratatui::restore();
|
||||
result
|
||||
.map_err(|e| anyhow::anyhow!("Error: {}", e))
|
||||
.map(|_| ())
|
||||
Ok(())
|
||||
}
|
||||
|
645
src/ui/editor.rs
645
src/ui/editor.rs
@ -1,14 +1,13 @@
|
||||
use std::{borrow::Borrow, io, path::PathBuf, thread::JoinHandle};
|
||||
use std::{borrow::Borrow, path::PathBuf, thread::JoinHandle};
|
||||
|
||||
use anyhow::Result;
|
||||
use ratatui::{widgets::Widget, DefaultTerminal, Frame};
|
||||
use eframe::egui;
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
|
||||
use crate::helper::getent::{get_groups, get_users, Group, User};
|
||||
|
||||
use posix_acl::{ACLEntry, PermSet, PosixACL, Qualifier, ACL_RWX};
|
||||
|
||||
use super::Component;
|
||||
|
||||
pub struct ACLEditor {
|
||||
pub path: PathBuf,
|
||||
pub is_changed: bool,
|
||||
@ -23,8 +22,8 @@ pub struct ACLEditor {
|
||||
pub save_thread_error: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for ACLEditor {
|
||||
fn default() -> Self {
|
||||
impl ACLEditor {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
path: PathBuf::new(),
|
||||
is_changed: false,
|
||||
@ -37,368 +36,322 @@ impl Default for ACLEditor {
|
||||
save_thread_error: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ACLEditor {
|
||||
fn render(&mut self, f: &mut Frame, rect: ratatui::prelude::Rect, evt: super::AppEventClient) {
|
||||
let block =
|
||||
|
||||
// todo!("Implement the rendering logic for ACLEditor");
|
||||
if let Some(save_thread) = &self.save_thread {
|
||||
if save_thread.is_finished() {
|
||||
let result = save_thread.join();
|
||||
match result {
|
||||
Ok(Ok(_)) => {
|
||||
f.set_title("Saved successfully!");
|
||||
self.save_thread_error = None;
|
||||
self.is_changed = false;
|
||||
pub fn show(&mut self, ui: &mut egui::Ui, selected_path: &PathBuf) {
|
||||
let mut show_save_or_dicard_message = false;
|
||||
ui.vertical(|ui| {
|
||||
if self.save_thread.is_some() {
|
||||
if self.save_thread.as_ref().unwrap().is_finished() {
|
||||
let save_thread = std::mem::replace(&mut self.save_thread, None);
|
||||
let result = save_thread.unwrap().join();
|
||||
match result {
|
||||
Ok(Ok(_)) => {
|
||||
ui.label("Saved successfully!");
|
||||
self.save_thread_error = None;
|
||||
self.is_changed = false;
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
self.save_thread_error = Some(format!("Failed to save {:?}", e));
|
||||
}
|
||||
Err(e) => {
|
||||
self.save_thread_error = Some(format!("Failed to join {:?}", e));
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
self.save_thread_error = Some(format!("Failed to save {:?}", e));
|
||||
}
|
||||
Err(e) => {
|
||||
self.save_thread_error = Some(format!("Failed to join {:?}", e));
|
||||
} else {
|
||||
egui::Frame::default()
|
||||
.fill(egui::Color32::GREEN)
|
||||
.inner_margin(4.0)
|
||||
.show(ui, |ui| {
|
||||
ui.label("Apply changes...");
|
||||
});
|
||||
}
|
||||
} else if let Some(err) = &self.save_thread_error.clone() {
|
||||
egui::Frame::default()
|
||||
.fill(egui::Color32::DARK_RED)
|
||||
.inner_margin(4.0)
|
||||
.show(ui, |ui| {
|
||||
ui.label(err);
|
||||
|
||||
if ui.button("OK").clicked() {
|
||||
self.save_thread_error = None;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ui.heading(format!("Editing: {:?}", self.path));
|
||||
ui.separator();
|
||||
|
||||
if self.path != *selected_path {
|
||||
if !self.is_changed {
|
||||
// Load ACLs
|
||||
self.path = selected_path.clone();
|
||||
self.acl = PosixACL::new_from_file(&self.path, false).unwrap();
|
||||
} else {
|
||||
show_save_or_dicard_message = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
f.set_title("Saving...");
|
||||
|
||||
self.acl_table(ui);
|
||||
|
||||
ui.separator();
|
||||
ui.label("Add new entry:");
|
||||
|
||||
self.new_user(ui);
|
||||
self.new_group(ui);
|
||||
|
||||
ui.separator();
|
||||
|
||||
if show_save_or_dicard_message {
|
||||
egui::Frame::default()
|
||||
.fill(egui::Color32::YELLOW)
|
||||
.inner_margin(4.0)
|
||||
.show(ui, |ui| {
|
||||
ui.label("You have unsaved changes! Save or discard to continue.");
|
||||
});
|
||||
}
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Save").clicked() {
|
||||
// TODO: Save ACLs
|
||||
let path = self.path.clone();
|
||||
let acl = self.acl.clone();
|
||||
self.save_thread_error = None;
|
||||
self.save_thread = Some(std::thread::spawn(move || {
|
||||
crate::helper::acl_writer::write_acl_recursive(path, acl)
|
||||
}));
|
||||
}
|
||||
|
||||
if ui.button("Discard").clicked() {
|
||||
self.is_changed = false;
|
||||
self.path.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if let Some(err) = &self.save_thread_error {
|
||||
f.set_title(err);
|
||||
} else {
|
||||
f.set_title(format!("Editing: {:?}", self.path));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ACLEditor {
|
||||
pub fn new(path: impl Into<PathBuf>) -> Self {
|
||||
fn new_group(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label("Group:");
|
||||
let selected = filter_dropbox(
|
||||
ui,
|
||||
"group_popup",
|
||||
&mut self.search_group,
|
||||
self.available_groups.iter(),
|
||||
|group, filter| group.name.contains(filter),
|
||||
|group| group.name.as_str(),
|
||||
);
|
||||
|
||||
let path: PathBuf = path.into();
|
||||
|
||||
Self {
|
||||
path,
|
||||
..Self::default()
|
||||
if let Some(selected) = selected {
|
||||
self.acl
|
||||
.entries
|
||||
.push(ACLEntry(Qualifier::Group(selected.gid), PermSet::ACL_RWX));
|
||||
self.is_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn show(&mut self, ui: &mut egui::Ui, selected_path: &PathBuf) {
|
||||
// let mut show_save_or_dicard_message = false;
|
||||
// ui.vertical(|ui| {
|
||||
// if self.save_thread.is_some() {
|
||||
// if self.save_thread.as_ref().unwrap().is_finished() {
|
||||
// let save_thread = std::mem::replace(&mut self.save_thread, None);
|
||||
// let result = save_thread.unwrap().join();
|
||||
// match result {
|
||||
// Ok(Ok(_)) => {
|
||||
// ui.label("Saved successfully!");
|
||||
// self.save_thread_error = None;
|
||||
// self.is_changed = false;
|
||||
// }
|
||||
// Ok(Err(e)) => {
|
||||
// self.save_thread_error = Some(format!("Failed to save {:?}", e));
|
||||
// }
|
||||
// Err(e) => {
|
||||
// self.save_thread_error = Some(format!("Failed to join {:?}", e));
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// egui::Frame::default()
|
||||
// .fill(egui::Color32::GREEN)
|
||||
// .inner_margin(4.0)
|
||||
// .show(ui, |ui| {
|
||||
// ui.label("Apply changes...");
|
||||
// });
|
||||
// }
|
||||
// } else if let Some(err) = &self.save_thread_error.clone() {
|
||||
// egui::Frame::default()
|
||||
// .fill(egui::Color32::DARK_RED)
|
||||
// .inner_margin(4.0)
|
||||
// .show(ui, |ui| {
|
||||
// ui.label(err);
|
||||
fn new_user(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label("User:");
|
||||
let selected = filter_dropbox(
|
||||
ui,
|
||||
"user_popup",
|
||||
&mut self.search_user,
|
||||
self.available_users.iter(),
|
||||
|user, filter| user.name.contains(filter),
|
||||
|user| user.name.as_str(),
|
||||
);
|
||||
|
||||
// if ui.button("OK").clicked() {
|
||||
// self.save_thread_error = None;
|
||||
// }
|
||||
// });
|
||||
// } else {
|
||||
// ui.heading(format!("Editing: {:?}", self.path));
|
||||
// ui.separator();
|
||||
if let Some(selected) = selected {
|
||||
self.acl
|
||||
.entries
|
||||
.push(ACLEntry(Qualifier::User(selected.uid), PermSet::ACL_RWX));
|
||||
self.is_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if self.path != *selected_path {
|
||||
// if !self.is_changed {
|
||||
// // Load ACLs
|
||||
// self.path = selected_path.clone();
|
||||
// self.acl = PosixACL::new_from_file(&self.path, false).unwrap();
|
||||
// } else {
|
||||
// show_save_or_dicard_message = true;
|
||||
// }
|
||||
// }
|
||||
fn acl_table(&mut self, ui: &mut egui::Ui) {
|
||||
TableBuilder::new(ui)
|
||||
.auto_shrink([false, true])
|
||||
.striped(true)
|
||||
.column(Column::auto().resizable(false))
|
||||
.column(Column::remainder().resizable(false))
|
||||
.column(Column::auto().resizable(false))
|
||||
.column(Column::auto().resizable(false))
|
||||
.column(Column::auto().resizable(false))
|
||||
.column(Column::auto().resizable(false))
|
||||
.header(20.0, |mut header| {
|
||||
header.col(|ui| {
|
||||
ui.heading("Type");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.heading("Name");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.heading("R");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.heading("W");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.heading("X");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.heading("D");
|
||||
});
|
||||
})
|
||||
.body(|mut body| {
|
||||
let mut to_delete: Vec<Qualifier> = Vec::new();
|
||||
|
||||
// self.acl_table(ui);
|
||||
self.acl.entries.sort();
|
||||
for entry in &mut self.acl.entries {
|
||||
body.row(30.0, |mut row| {
|
||||
row.col(|ui| {
|
||||
let label = match entry.0 {
|
||||
Qualifier::User(_) => "U",
|
||||
Qualifier::Group(_) => "G",
|
||||
Qualifier::Other => "",
|
||||
Qualifier::GroupObj => "G",
|
||||
Qualifier::UserObj => "U",
|
||||
Qualifier::Mask => "",
|
||||
};
|
||||
ui.label(label);
|
||||
});
|
||||
row.col(|ui| {
|
||||
let label = match entry.0 {
|
||||
Qualifier::User(uid) => self
|
||||
.available_users
|
||||
.iter()
|
||||
.find(|user| user.uid == uid)
|
||||
.map(|user| user.name.clone())
|
||||
.unwrap_or_else(|| {
|
||||
self.available_groups
|
||||
.iter()
|
||||
.find(|group| group.gid == uid)
|
||||
.map(|group| format!("({})", group.name))
|
||||
.unwrap_or_else(|| format!("Unknown user {}", uid))
|
||||
}),
|
||||
Qualifier::Group(gid) => self
|
||||
.available_groups
|
||||
.iter()
|
||||
.find(|group| group.gid == gid)
|
||||
.map(|group| group.name.clone())
|
||||
.unwrap_or_else(|| {
|
||||
self.available_users
|
||||
.iter()
|
||||
.find(|user| user.uid == gid)
|
||||
.map(|user| format!("({})", user.name))
|
||||
.unwrap_or_else(|| format!("Unknown group {}", gid))
|
||||
}),
|
||||
Qualifier::Other => "Other".to_string(),
|
||||
Qualifier::GroupObj => "GroupObj".to_string(),
|
||||
Qualifier::UserObj => "UserObj".to_string(),
|
||||
Qualifier::Mask => "Mask".to_string(),
|
||||
};
|
||||
|
||||
// ui.separator();
|
||||
// ui.label("Add new entry:");
|
||||
ui.label(label);
|
||||
});
|
||||
row.col(|ui| {
|
||||
let mut checked = entry.1.contains(PermSet::ACL_READ);
|
||||
if ui.checkbox(&mut checked, "").changed() {
|
||||
if checked {
|
||||
entry.1.insert(PermSet::ACL_READ);
|
||||
self.is_changed = true;
|
||||
} else {
|
||||
entry.1.remove(PermSet::ACL_READ);
|
||||
self.is_changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
row.col(|ui| {
|
||||
let mut checked = entry.1.contains(PermSet::ACL_WRITE);
|
||||
if ui.checkbox(&mut checked, "").changed() {
|
||||
if checked {
|
||||
entry.1.insert(PermSet::ACL_WRITE);
|
||||
self.is_changed = true;
|
||||
} else {
|
||||
entry.1.remove(PermSet::ACL_WRITE);
|
||||
self.is_changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
row.col(|ui| {
|
||||
let mut checked = entry.1.contains(PermSet::ACL_EXECUTE);
|
||||
if ui.checkbox(&mut checked, "").changed() {
|
||||
if checked {
|
||||
entry.1.insert(PermSet::ACL_EXECUTE);
|
||||
self.is_changed = true;
|
||||
} else {
|
||||
entry.1.remove(PermSet::ACL_EXECUTE);
|
||||
self.is_changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// self.new_user(ui);
|
||||
// self.new_group(ui);
|
||||
row.col(|ui| match entry.0 {
|
||||
Qualifier::User(_) | Qualifier::Group(_) => {
|
||||
if ui.button("🗑").clicked() {
|
||||
to_delete.push(entry.0.clone());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ui.separator();
|
||||
|
||||
// if show_save_or_dicard_message {
|
||||
// egui::Frame::default()
|
||||
// .fill(egui::Color32::YELLOW)
|
||||
// .inner_margin(4.0)
|
||||
// .show(ui, |ui| {
|
||||
// ui.label("You have unsaved changes! Save or discard to continue.");
|
||||
// });
|
||||
// }
|
||||
|
||||
// ui.horizontal(|ui| {
|
||||
// if ui.button("Save").clicked() {
|
||||
// let path = self.path.clone();
|
||||
// let mut acl = self.acl.clone();
|
||||
// acl.set(ACLEntry(Qualifier::Mask, PermSet::all())); // Make sure mask is set!
|
||||
|
||||
// self.save_thread_error = None;
|
||||
// self.save_thread = Some(std::thread::spawn(move || {
|
||||
// crate::helper::acl_writer::write_acl_recursive(path, acl)
|
||||
// }));
|
||||
// }
|
||||
|
||||
// if ui.button("Discard").clicked() {
|
||||
// self.is_changed = false;
|
||||
// self.path.clear();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// fn new_group(&mut self, ui: &mut egui::Ui) {
|
||||
// ui.label("Group:");
|
||||
// let selected = filter_dropbox(
|
||||
// ui,
|
||||
// "group_popup",
|
||||
// &mut self.search_group,
|
||||
// self.available_groups.iter(),
|
||||
// |group, filter| group.name.contains(filter),
|
||||
// |group| group.name.as_str(),
|
||||
// );
|
||||
|
||||
// if let Some(selected) = selected {
|
||||
// self.acl
|
||||
// .entries
|
||||
// .push(ACLEntry(Qualifier::Group(selected.gid), PermSet::ACL_RWX));
|
||||
// self.is_changed = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn new_user(&mut self, ui: &mut egui::Ui) {
|
||||
// ui.label("User:");
|
||||
// let selected = filter_dropbox(
|
||||
// ui,
|
||||
// "user_popup",
|
||||
// &mut self.search_user,
|
||||
// self.available_users.iter(),
|
||||
// |user, filter| user.name.contains(filter),
|
||||
// |user| user.name.as_str(),
|
||||
// );
|
||||
|
||||
// if let Some(selected) = selected {
|
||||
// self.acl
|
||||
// .entries
|
||||
// .push(ACLEntry(Qualifier::User(selected.uid), PermSet::ACL_RWX));
|
||||
// self.is_changed = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn acl_table(&mut self, ui: &mut egui::Ui) {
|
||||
// TableBuilder::new(ui)
|
||||
// .auto_shrink([false, true])
|
||||
// .striped(true)
|
||||
// .column(Column::auto().resizable(false))
|
||||
// .column(Column::remainder().resizable(false))
|
||||
// .column(Column::auto().resizable(false))
|
||||
// .column(Column::auto().resizable(false))
|
||||
// .column(Column::auto().resizable(false))
|
||||
// .column(Column::auto().resizable(false))
|
||||
// .header(20.0, |mut header| {
|
||||
// header.col(|ui| {
|
||||
// ui.heading("Type");
|
||||
// });
|
||||
// header.col(|ui| {
|
||||
// ui.heading("Name");
|
||||
// });
|
||||
// header.col(|ui| {
|
||||
// ui.heading("R");
|
||||
// });
|
||||
// header.col(|ui| {
|
||||
// ui.heading("W");
|
||||
// });
|
||||
// header.col(|ui| {
|
||||
// ui.heading("X");
|
||||
// });
|
||||
// header.col(|ui| {
|
||||
// ui.heading("D");
|
||||
// });
|
||||
// })
|
||||
// .body(|mut body| {
|
||||
// let mut to_delete: Vec<Qualifier> = Vec::new();
|
||||
|
||||
// self.acl.entries.sort();
|
||||
// for entry in &mut self.acl.entries {
|
||||
// body.row(30.0, |mut row| {
|
||||
// row.col(|ui| {
|
||||
// let label = match entry.0 {
|
||||
// Qualifier::User(_) => "U",
|
||||
// Qualifier::Group(_) => "G",
|
||||
// Qualifier::Other => "",
|
||||
// Qualifier::GroupObj => "G",
|
||||
// Qualifier::UserObj => "U",
|
||||
// Qualifier::Mask => "",
|
||||
// };
|
||||
// ui.label(label);
|
||||
// });
|
||||
// row.col(|ui| {
|
||||
// let label = match entry.0 {
|
||||
// Qualifier::User(uid) => self
|
||||
// .available_users
|
||||
// .iter()
|
||||
// .find(|user| user.uid == uid)
|
||||
// .map(|user| user.name.clone())
|
||||
// .unwrap_or_else(|| {
|
||||
// self.available_groups
|
||||
// .iter()
|
||||
// .find(|group| group.gid == uid)
|
||||
// .map(|group| format!("({})", group.name))
|
||||
// .unwrap_or_else(|| format!("Unknown user {}", uid))
|
||||
// }),
|
||||
// Qualifier::Group(gid) => self
|
||||
// .available_groups
|
||||
// .iter()
|
||||
// .find(|group| group.gid == gid)
|
||||
// .map(|group| group.name.clone())
|
||||
// .unwrap_or_else(|| {
|
||||
// self.available_users
|
||||
// .iter()
|
||||
// .find(|user| user.uid == gid)
|
||||
// .map(|user| format!("({})", user.name))
|
||||
// .unwrap_or_else(|| format!("Unknown group {}", gid))
|
||||
// }),
|
||||
// Qualifier::Other => "Other".to_string(),
|
||||
// Qualifier::GroupObj => "GroupObj".to_string(),
|
||||
// Qualifier::UserObj => "UserObj".to_string(),
|
||||
// Qualifier::Mask => "Mask".to_string(),
|
||||
// };
|
||||
|
||||
// ui.label(label);
|
||||
// });
|
||||
// row.col(|ui| {
|
||||
// let mut checked = entry.1.contains(PermSet::ACL_READ);
|
||||
// if ui.checkbox(&mut checked, "").changed() {
|
||||
// if checked {
|
||||
// entry.1.insert(PermSet::ACL_READ);
|
||||
// self.is_changed = true;
|
||||
// } else {
|
||||
// entry.1.remove(PermSet::ACL_READ);
|
||||
// self.is_changed = true;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// row.col(|ui| {
|
||||
// let mut checked = entry.1.contains(PermSet::ACL_WRITE);
|
||||
// if ui.checkbox(&mut checked, "").changed() {
|
||||
// if checked {
|
||||
// entry.1.insert(PermSet::ACL_WRITE);
|
||||
// self.is_changed = true;
|
||||
// } else {
|
||||
// entry.1.remove(PermSet::ACL_WRITE);
|
||||
// self.is_changed = true;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// row.col(|ui| {
|
||||
// let mut checked = entry.1.contains(PermSet::ACL_EXECUTE);
|
||||
// if ui.checkbox(&mut checked, "").changed() {
|
||||
// if checked {
|
||||
// entry.1.insert(PermSet::ACL_EXECUTE);
|
||||
// self.is_changed = true;
|
||||
// } else {
|
||||
// entry.1.remove(PermSet::ACL_EXECUTE);
|
||||
// self.is_changed = true;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// row.col(|ui| match entry.0 {
|
||||
// Qualifier::User(_) | Qualifier::Group(_) => {
|
||||
// if ui.button("🗑").clicked() {
|
||||
// to_delete.push(entry.0.clone());
|
||||
// }
|
||||
// }
|
||||
// _ => {}
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// for entry in to_delete {
|
||||
// self.acl.entries.retain(|e| e.0 != entry);
|
||||
// self.is_changed = true;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
for entry in to_delete {
|
||||
self.acl.entries.retain(|e| e.0 != entry);
|
||||
self.is_changed = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// fn searchable_dropdown()
|
||||
|
||||
// fn filter_dropbox<
|
||||
// 'a,
|
||||
// T: PartialEq + 'a,
|
||||
// I: Iterator<Item = &'a T> + 'a,
|
||||
// F: Fn(&'a T, &str) -> bool,
|
||||
// L: Fn(&'a T) -> &str,
|
||||
// >(
|
||||
// ui: &mut egui::Ui,
|
||||
// popup_id: &str,
|
||||
// search_buf: &mut String,
|
||||
// items: I,
|
||||
// filter_items: F,
|
||||
// get_label: L,
|
||||
// ) -> Option<&'a T> {
|
||||
// let search_field = ui.text_edit_singleline(search_buf);
|
||||
// let popup_id = ui.make_persistent_id(popup_id);
|
||||
fn filter_dropbox<
|
||||
'a,
|
||||
T: PartialEq + 'a,
|
||||
I: Iterator<Item = &'a T> + 'a,
|
||||
F: Fn(&'a T, &str) -> bool,
|
||||
L: Fn(&'a T) -> &str,
|
||||
>(
|
||||
ui: &mut egui::Ui,
|
||||
popup_id: &str,
|
||||
search_buf: &mut String,
|
||||
items: I,
|
||||
filter_items: F,
|
||||
get_label: L,
|
||||
) -> Option<&'a T> {
|
||||
let search_field = ui.text_edit_singleline(search_buf);
|
||||
let popup_id = ui.make_persistent_id(popup_id);
|
||||
|
||||
// if search_field.gained_focus() {
|
||||
// ui.memory_mut(|mem| mem.open_popup(popup_id));
|
||||
// }
|
||||
if search_field.gained_focus() {
|
||||
ui.memory_mut(|mem| mem.open_popup(popup_id));
|
||||
}
|
||||
|
||||
// let mut selected = None;
|
||||
// egui::popup_above_or_below_widget(
|
||||
// &ui,
|
||||
// popup_id,
|
||||
// &search_field,
|
||||
// egui::AboveOrBelow::Below,
|
||||
// egui::PopupCloseBehavior::CloseOnClick,
|
||||
// |ui| {
|
||||
// ui.set_min_width(200.0);
|
||||
// ui.set_max_height(300.0);
|
||||
// egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
// let filtered = items.filter(|elm| filter_items(elm, &search_buf));
|
||||
let mut selected = None;
|
||||
egui::popup_above_or_below_widget(
|
||||
&ui,
|
||||
popup_id,
|
||||
&search_field,
|
||||
egui::AboveOrBelow::Below,
|
||||
egui::PopupCloseBehavior::CloseOnClick,
|
||||
|ui| {
|
||||
ui.set_min_width(200.0);
|
||||
ui.set_max_height(300.0);
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
let filtered = items.filter(|elm| filter_items(elm, &search_buf));
|
||||
|
||||
// for user in filtered {
|
||||
// let value = ui.selectable_value(&mut selected, Some(user), get_label(user));
|
||||
// if value.clicked() {
|
||||
// search_field.surrender_focus();
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// },
|
||||
// );
|
||||
for user in filtered {
|
||||
let value = ui.selectable_value(&mut selected, Some(user), get_label(user));
|
||||
if value.clicked() {
|
||||
search_field.surrender_focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// if selected.is_some() {
|
||||
// search_buf.clear();
|
||||
// }
|
||||
if selected.is_some() {
|
||||
search_buf.clear();
|
||||
}
|
||||
|
||||
// selected
|
||||
// }
|
||||
selected
|
||||
}
|
||||
|
235
src/ui/mod.rs
235
src/ui/mod.rs
@ -1,217 +1,68 @@
|
||||
use std::{io, rc::Rc, sync::RwLock};
|
||||
|
||||
use anyhow::Result;
|
||||
use editor::ACLEditor;
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
widgets::Widget,
|
||||
DefaultTerminal, Frame,
|
||||
};
|
||||
use eframe::egui::{self};
|
||||
use tree::Tree;
|
||||
|
||||
mod editor;
|
||||
mod tree;
|
||||
|
||||
pub struct App {
|
||||
exit: bool,
|
||||
tree: Tree,
|
||||
editor: Option<ACLEditor>,
|
||||
editor: ACLEditor,
|
||||
}
|
||||
|
||||
impl Default for App {
|
||||
fn default() -> Self {
|
||||
impl App {
|
||||
pub fn new(ctx: &egui::Context) -> Self {
|
||||
let mut fonts = egui::FontDefinitions::default();
|
||||
fonts.font_data.insert(
|
||||
"FiraCode".to_owned(),
|
||||
egui::FontData::from_static(include_bytes!("../../fonts/FiraCodeNerdFont-Regular.ttf")),
|
||||
);
|
||||
|
||||
fonts
|
||||
.families
|
||||
.entry(egui::FontFamily::Monospace)
|
||||
.or_default()
|
||||
.insert(0, "FiraCode".to_owned());
|
||||
|
||||
fonts
|
||||
.families
|
||||
.entry(egui::FontFamily::Proportional)
|
||||
.or_default()
|
||||
.insert(0, "FiraCode".to_owned());
|
||||
|
||||
ctx.set_fonts(fonts);
|
||||
|
||||
// first argument is the initial path or the current working directory
|
||||
|
||||
let initial_path = if let Some(path) = std::env::args().nth(1) {
|
||||
std::path::PathBuf::from(path)
|
||||
} else {
|
||||
std::env::current_dir().unwrap()
|
||||
};
|
||||
|
||||
Self {
|
||||
exit: false,
|
||||
App {
|
||||
tree: Tree::new(initial_path),
|
||||
editor: None,
|
||||
editor: ACLEditor::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
|
||||
let mut app_event = AppEventHost::default();
|
||||
while !self.exit {
|
||||
terminal.draw(|frame| self.render(frame, app_event))?;
|
||||
app_event = self.handle_events()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
impl eframe::App for App {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::SidePanel::right("side_panel")
|
||||
.min_width(300.0)
|
||||
.max_width(ctx.available_rect().width() - 100.0)
|
||||
.resizable(true)
|
||||
.show(ctx, |ui| {
|
||||
self.editor.show(ui, &self.tree.selected);
|
||||
});
|
||||
|
||||
pub fn handle_events(&mut self) -> io::Result<AppEventHost> {
|
||||
// Handle events here
|
||||
// For example, you can check for key presses and update the state accordingly
|
||||
|
||||
let evt = event::read()?;
|
||||
if let Event::Key(key) = evt {
|
||||
match key.code {
|
||||
KeyCode::Char('q') => {
|
||||
self.exit = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(AppEventHost::new(Some(evt)))
|
||||
}
|
||||
|
||||
pub fn render(&mut self, frame: &mut Frame, event: AppEventHost) {
|
||||
if let Some(evt) = &event.event {
|
||||
match evt {
|
||||
Event::Key(key) => match key.code {
|
||||
KeyCode::Char('q') => {
|
||||
self.exit = true;
|
||||
}
|
||||
KeyCode::Enter | KeyCode::Char('e') => {
|
||||
self.editor = Some(ACLEditor::new(self.tree.get_selected()));
|
||||
}
|
||||
KeyCode::Esc | KeyCode::Char('c') => {
|
||||
self.editor = None;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
event.get_client(true).register_helper("q", "Quit");
|
||||
|
||||
let layout1 = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Fill(1), Constraint::Length(1)])
|
||||
.split(frame.area());
|
||||
|
||||
let content_area = layout1[0];
|
||||
let help_area = layout1[1];
|
||||
|
||||
let layout2 = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(30), Constraint::Fill(1)])
|
||||
.split(content_area);
|
||||
|
||||
let content_left_area = layout2[0];
|
||||
let content_right_area = layout2[1];
|
||||
|
||||
self.tree.render(
|
||||
frame,
|
||||
content_left_area,
|
||||
event.get_client(self.editor.is_none()),
|
||||
);
|
||||
|
||||
if let Some(editor) = self.editor.as_mut() {
|
||||
event
|
||||
.get_client(true)
|
||||
.register_helper("Esc", "Close editor");
|
||||
editor.render(frame, content_right_area, event.get_client(true));
|
||||
} else {
|
||||
event.get_client(true).register_helper("Enter", "Select");
|
||||
frame.render_widget("Select folder from the left", content_right_area);
|
||||
}
|
||||
|
||||
event.render_help(frame, help_area);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppEventHost {
|
||||
pub event: Option<Event>,
|
||||
pub registered_helper: Rc<RwLock<Vec<AppEventHelper>>>,
|
||||
}
|
||||
|
||||
impl Default for AppEventHost {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
event: None,
|
||||
registered_helper: Rc::from(RwLock::new(vec![])),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppEventHost {
|
||||
pub fn new(event: Option<Event>) -> Self {
|
||||
Self {
|
||||
event: event,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_help(&self, frame: &mut Frame, area: Rect) {
|
||||
let mut help = String::new();
|
||||
let infos = self.registered_helper.read().unwrap();
|
||||
for info in infos.iter() {
|
||||
help.push_str(&format!("{}: {} | \n", info.key, info.help));
|
||||
}
|
||||
frame.render_widget(help, area);
|
||||
}
|
||||
|
||||
fn get_client(&self, focus: bool) -> AppEventClient {
|
||||
AppEventClient {
|
||||
host: self.registered_helper.clone(),
|
||||
event: self.event.clone(),
|
||||
has_focus: focus,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppEventClient {
|
||||
host: Rc<RwLock<Vec<AppEventHelper>>>,
|
||||
event: Option<Event>,
|
||||
pub has_focus: bool,
|
||||
}
|
||||
|
||||
impl AppEventClient {
|
||||
pub fn has_focus(&self) -> bool {
|
||||
self.has_focus
|
||||
}
|
||||
|
||||
pub fn get_client(&self, focus: bool) -> Self {
|
||||
Self {
|
||||
host: self.host.clone(),
|
||||
event: self.event.clone(),
|
||||
has_focus: focus,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_helper(&mut self, key: &str, help: &str) {
|
||||
self.host.write().unwrap().push(AppEventHelper {
|
||||
key: key.to_string(),
|
||||
help: help.to_string(),
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::ScrollArea::both()
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
self.tree.show(ui);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppEventHelper {
|
||||
pub key: String,
|
||||
pub help: String,
|
||||
}
|
||||
|
||||
// impl eframe::App for App {
|
||||
// fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
// egui::SidePanel::right("side_panel")
|
||||
// .min_width(300.0)
|
||||
// .max_width(ctx.available_rect().width() - 100.0)
|
||||
// .resizable(true)
|
||||
// .show(ctx, |ui| {
|
||||
// self.editor.show(ui, &self.tree.selected);
|
||||
// });
|
||||
|
||||
// egui::CentralPanel::default().show(ctx, |ui| {
|
||||
// egui::ScrollArea::both()
|
||||
// .auto_shrink([false; 2])
|
||||
// .show(ui, |ui| {
|
||||
// self.tree.show(ui);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
pub trait Component {
|
||||
fn render(&mut self, f: &mut Frame, rect: Rect, evt: AppEventClient);
|
||||
}
|
||||
|
324
src/ui/tree.rs
324
src/ui/tree.rs
@ -3,98 +3,13 @@
|
||||
// Folders can by selected
|
||||
// Folders will fetch their children when expanded
|
||||
|
||||
use std::{default, fmt::format, path::PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use ratatui::{
|
||||
crossterm::event::{Event, KeyCode, KeyEvent},
|
||||
style::{Color, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, Widget},
|
||||
Frame,
|
||||
};
|
||||
|
||||
use super::Component;
|
||||
|
||||
// use eframe::egui;
|
||||
use eframe::egui;
|
||||
|
||||
pub struct Tree {
|
||||
root: Folder,
|
||||
// pub selected: PathBuf,
|
||||
vertical_scroll: usize,
|
||||
vertical_scroll_state: ScrollbarState,
|
||||
selected_idx: usize,
|
||||
}
|
||||
|
||||
impl Component for Tree {
|
||||
fn render(
|
||||
&mut self,
|
||||
f: &mut ratatui::Frame,
|
||||
rect: ratatui::prelude::Rect,
|
||||
mut evt: super::AppEventClient,
|
||||
) {
|
||||
if evt.has_focus() {
|
||||
if let Some(evt) = &evt.event {
|
||||
if let Event::Key(key) = evt {
|
||||
match key.code {
|
||||
KeyCode::Up => {
|
||||
if self.selected_idx > 0 {
|
||||
self.selected_idx -= 1;
|
||||
}
|
||||
}
|
||||
KeyCode::Down => {
|
||||
let entries = self.root.get_entries();
|
||||
if self.selected_idx < entries.len() - 1 {
|
||||
self.selected_idx += 1;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
evt.register_helper("->", "Expand");
|
||||
evt.register_helper("<-", "Collapse");
|
||||
evt.register_helper("↑", "Up");
|
||||
evt.register_helper("↓", "Down");
|
||||
}
|
||||
|
||||
let block = Block::bordered()
|
||||
.title("Select Folder")
|
||||
.borders(ratatui::widgets::Borders::ALL)
|
||||
.border_style(ratatui::style::Style::default().fg(ratatui::style::Color::White));
|
||||
|
||||
let inner_area = block.inner(rect);
|
||||
f.render_widget(block, rect);
|
||||
|
||||
let mut tmp = self.selected_idx;
|
||||
self.root.apply_event(&evt, &mut tmp);
|
||||
let entries = self.root.get_entries();
|
||||
let lines: Vec<Line> = entries.iter().map(|entry| entry.get_line()).collect();
|
||||
|
||||
if self.selected_idx >= self.vertical_scroll + inner_area.height as usize {
|
||||
self.vertical_scroll = self.selected_idx - inner_area.height as usize + 1;
|
||||
} else if self.selected_idx < self.vertical_scroll as usize {
|
||||
self.vertical_scroll = self.selected_idx;
|
||||
}
|
||||
|
||||
self.vertical_scroll_state = self
|
||||
.vertical_scroll_state
|
||||
.content_length(lines.len())
|
||||
.position(self.vertical_scroll as usize);
|
||||
|
||||
let paragraph = Paragraph::new(lines).scroll((self.vertical_scroll as u16, 0));
|
||||
|
||||
f.render_widget(paragraph, inner_area);
|
||||
f.render_stateful_widget(
|
||||
Scrollbar::new(ScrollbarOrientation::VerticalRight)
|
||||
.begin_symbol(Some("↑"))
|
||||
.end_symbol(Some("↓")),
|
||||
inner_area,
|
||||
&mut self.vertical_scroll_state,
|
||||
);
|
||||
|
||||
// todo!("Implement the Widget trait for Tree");
|
||||
}
|
||||
pub selected: PathBuf,
|
||||
}
|
||||
|
||||
impl Tree {
|
||||
@ -102,31 +17,25 @@ impl Tree {
|
||||
let path: PathBuf = initial_path.into();
|
||||
let name = path.to_string_lossy().to_string();
|
||||
|
||||
let mut root = Folder::new(name, path, 0);
|
||||
root.selected = true;
|
||||
|
||||
Self {
|
||||
root,
|
||||
// selected: PathBuf::new(),
|
||||
vertical_scroll: 0,
|
||||
vertical_scroll_state: ScrollbarState::default(),
|
||||
selected_idx: 0,
|
||||
root: Folder::new(name, path),
|
||||
selected: PathBuf::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_selected(&self) -> PathBuf {
|
||||
let entries = self.root.get_entries();
|
||||
if self.selected_idx < entries.len() {
|
||||
return entries[self.selected_idx].path.clone();
|
||||
}
|
||||
PathBuf::new()
|
||||
pub fn set_root_path(&mut self, path: impl Into<PathBuf>) {
|
||||
let path: PathBuf = path.into();
|
||||
|
||||
let name = path.to_string_lossy().to_string();
|
||||
|
||||
self.root = Folder::new(name, path);
|
||||
}
|
||||
|
||||
// pub fn show(&mut self, ui: &mut egui::Ui) {
|
||||
// ui.vertical(|ui| {
|
||||
// self.root.show(ui, &mut self.selected);
|
||||
// });
|
||||
// }
|
||||
pub fn show(&mut self, ui: &mut egui::Ui) {
|
||||
ui.vertical(|ui| {
|
||||
self.root.show(ui, &mut self.selected);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Folder {
|
||||
@ -134,186 +43,83 @@ pub struct Folder {
|
||||
path: PathBuf,
|
||||
expanded: bool,
|
||||
children: Option<Vec<Folder>>,
|
||||
selected: bool,
|
||||
depth: usize,
|
||||
// selected: Arc<RwLock<PathBuf>>,
|
||||
}
|
||||
|
||||
impl Folder {
|
||||
pub fn new(name: impl Into<String>, path: impl Into<PathBuf>, depth: usize) -> Self {
|
||||
pub fn new(name: impl Into<String>, path: impl Into<PathBuf>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
path: path.into(),
|
||||
expanded: false,
|
||||
selected: false,
|
||||
children: Default::default(),
|
||||
depth,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand(&mut self) {
|
||||
if self.children.is_none() {
|
||||
pub fn show(&mut self, ui: &mut egui::Ui, selected: &mut PathBuf) {
|
||||
let Self {
|
||||
name,
|
||||
path,
|
||||
expanded,
|
||||
children,
|
||||
} = self;
|
||||
|
||||
if children.is_none() {
|
||||
let mut children_data = vec![];
|
||||
// println!("Reading dir: {:?}", self.path);
|
||||
std::fs::read_dir(&self.path)
|
||||
println!("Reading dir: {:?}", path);
|
||||
std::fs::read_dir(&path)
|
||||
.map(|entries| {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
// println!(
|
||||
// "Found dir: {:?}, file {}, link {}",
|
||||
// path,
|
||||
// path.is_file(),
|
||||
// path.is_symlink()
|
||||
// );
|
||||
println!(
|
||||
"Found dir: {:?}, file {}, link {}",
|
||||
path,
|
||||
path.is_file(),
|
||||
path.is_symlink()
|
||||
);
|
||||
let path2 = path.clone();
|
||||
let name = path2.file_name().unwrap().to_string_lossy();
|
||||
children_data.push(Folder::new(name, path, self.depth + 1));
|
||||
children_data.push(Folder::new(name, path));
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Failed to read dir: {}", err);
|
||||
});
|
||||
children_data.sort_by_key(|folder| folder.name.clone());
|
||||
self.children.replace(children_data);
|
||||
children.replace(children_data);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_event(&mut self, evt: &super::AppEventClient, selected_idx: &mut usize) {
|
||||
if *selected_idx == 0 {
|
||||
self.selected = true;
|
||||
*selected_idx = usize::MAX;
|
||||
} else {
|
||||
self.selected = false;
|
||||
}
|
||||
*selected_idx -= 1;
|
||||
if evt.has_focus() && self.selected {
|
||||
if let Some(evt) = &evt.event {
|
||||
if let Event::Key(key) = evt {
|
||||
match key.code {
|
||||
KeyCode::Right | KeyCode::Enter => {
|
||||
self.expand();
|
||||
self.expanded = true;
|
||||
}
|
||||
KeyCode::Left => {
|
||||
self.expanded = false;
|
||||
}
|
||||
_ => {}
|
||||
ui.horizontal(|ui| {
|
||||
if let Some(children) = children {
|
||||
if !children.is_empty() {
|
||||
let label = if *expanded { "▼" } else { "▶" };
|
||||
let response = ui.selectable_label(false, label);
|
||||
|
||||
if response.clicked() && !children.is_empty() {
|
||||
*expanded = !*expanded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.expanded {
|
||||
if let Some(children) = &mut self.children {
|
||||
for child in children {
|
||||
child.apply_event(evt, selected_idx);
|
||||
// if child.selected {
|
||||
// self.selected = true;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_entries(&self) -> Vec<&Folder> {
|
||||
let mut entries: Vec<&Folder> = vec![];
|
||||
if self.expanded {
|
||||
if let Some(children) = &self.children {
|
||||
for child in children {
|
||||
entries.extend(child.get_entries());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut res: Vec<&Folder> = vec![self];
|
||||
res.extend(entries);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn get_line(&self) -> Line {
|
||||
let pad = " ".repeat(self.depth);
|
||||
Line::from(vec![Span::styled(
|
||||
format!(
|
||||
"{}{} {}",
|
||||
pad,
|
||||
if self.expanded { "▼" } else { "▶" },
|
||||
self.name
|
||||
),
|
||||
if self.selected {
|
||||
Style::new().bg(Color::White).fg(Color::Black)
|
||||
} else {
|
||||
Style::default()
|
||||
},
|
||||
)])
|
||||
ui.label("⌛");
|
||||
}
|
||||
|
||||
if ui
|
||||
.selectable_label(*selected == *path, name.to_owned())
|
||||
.clicked()
|
||||
{
|
||||
*selected = path.clone();
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(children) = children {
|
||||
if *expanded {
|
||||
ui.indent("", |ui| {
|
||||
for child in children {
|
||||
child.show(ui, selected);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn show(&mut self, ui: &mut egui::Ui, selected: &mut PathBuf) {
|
||||
// let Self {
|
||||
// name,
|
||||
// path,
|
||||
// expanded,
|
||||
// children,
|
||||
// } = self;
|
||||
|
||||
// if children.is_none() {
|
||||
// let mut children_data = vec![];
|
||||
// println!("Reading dir: {:?}", path);
|
||||
// std::fs::read_dir(&path)
|
||||
// .map(|entries| {
|
||||
// for entry in entries.flatten() {
|
||||
// let path = entry.path();
|
||||
// if path.is_dir() {
|
||||
// println!(
|
||||
// "Found dir: {:?}, file {}, link {}",
|
||||
// path,
|
||||
// path.is_file(),
|
||||
// path.is_symlink()
|
||||
// );
|
||||
// let path2 = path.clone();
|
||||
// let name = path2.file_name().unwrap().to_string_lossy();
|
||||
// children_data.push(Folder::new(name, path));
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// .unwrap_or_else(|err| {
|
||||
// eprintln!("Failed to read dir: {}", err);
|
||||
// });
|
||||
// children_data.sort_by_key(|folder| folder.name.clone());
|
||||
// children.replace(children_data);
|
||||
// }
|
||||
|
||||
// ui.horizontal(|ui| {
|
||||
// if let Some(children) = children {
|
||||
// if !children.is_empty() {
|
||||
// let label = if *expanded { "▼" } else { "▶" };
|
||||
// let response = ui.selectable_label(false, label);
|
||||
|
||||
// if response.clicked() && !children.is_empty() {
|
||||
// *expanded = !*expanded;
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// ui.label("⌛");
|
||||
// }
|
||||
|
||||
// if ui
|
||||
// .selectable_label(*selected == *path, name.to_owned())
|
||||
// .clicked()
|
||||
// {
|
||||
// *selected = path.clone();
|
||||
// }
|
||||
// });
|
||||
|
||||
// if let Some(children) = children {
|
||||
// if *expanded {
|
||||
// ui.indent("", |ui| {
|
||||
// for child in children {
|
||||
// child.show(ui, selected);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user