refact: multi-selection to per cmd (#719)

* Add per cmd multi-selection

* Add colors for nm cmds

* fix: conflicts
This commit is contained in:
Jeevitha Kannan K S 2024-11-06 01:29:57 +05:30 committed by GitHub
parent 48e8bab12c
commit 67b749942c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 76 additions and 61 deletions

View File

@ -33,30 +33,18 @@ pub fn get_tabs(validate: bool) -> (TempDir, Vec<Tab>) {
let tabs: Vec<Tab> = tabs let tabs: Vec<Tab> = tabs
.into_iter() .into_iter()
.map( .map(|(TabEntry { name, data }, directory)| {
|( let mut tree = Tree::new(Rc::new(ListNode {
TabEntry { name: "root".to_string(),
name, description: String::new(),
data, command: Command::None,
multi_selectable, task_list: String::new(),
}, multi_select: false,
directory, }));
)| { let mut root = tree.root_mut();
let mut tree = Tree::new(Rc::new(ListNode { create_directory(data, &mut root, &directory, validate, true);
name: "root".to_string(), Tab { name, tree }
description: String::new(), })
command: Command::None,
task_list: String::new(),
}));
let mut root = tree.root_mut();
create_directory(data, &mut root, &directory, validate);
Tab {
name,
tree,
multi_selectable,
}
},
)
.collect(); .collect();
if tabs.is_empty() { if tabs.is_empty() {
@ -74,12 +62,6 @@ struct TabList {
struct TabEntry { struct TabEntry {
name: String, name: String,
data: Vec<Entry>, data: Vec<Entry>,
#[serde(default = "default_multi_selectable")]
multi_selectable: bool,
}
fn default_multi_selectable() -> bool {
true
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -94,6 +76,12 @@ struct Entry {
entry_type: EntryType, entry_type: EntryType,
#[serde(default)] #[serde(default)]
task_list: String, task_list: String,
#[serde(default = "default_true")]
multi_select: bool,
}
fn default_true() -> bool {
true
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -174,8 +162,11 @@ fn create_directory(
node: &mut NodeMut<Rc<ListNode>>, node: &mut NodeMut<Rc<ListNode>>,
command_dir: &Path, command_dir: &Path,
validate: bool, validate: bool,
parent_multi_select: bool,
) { ) {
for entry in data { for entry in data {
let multi_select = parent_multi_select && entry.multi_select;
match entry.entry_type { match entry.entry_type {
EntryType::Entries(entries) => { EntryType::Entries(entries) => {
let mut node = node.append(Rc::new(ListNode { let mut node = node.append(Rc::new(ListNode {
@ -183,8 +174,9 @@ fn create_directory(
description: entry.description, description: entry.description,
command: Command::None, command: Command::None,
task_list: String::new(), task_list: String::new(),
multi_select,
})); }));
create_directory(entries, &mut node, command_dir, validate); create_directory(entries, &mut node, command_dir, validate, multi_select);
} }
EntryType::Command(command) => { EntryType::Command(command) => {
node.append(Rc::new(ListNode { node.append(Rc::new(ListNode {
@ -192,6 +184,7 @@ fn create_directory(
description: entry.description, description: entry.description,
command: Command::Raw(command), command: Command::Raw(command),
task_list: String::new(), task_list: String::new(),
multi_select,
})); }));
} }
EntryType::Script(script) => { EntryType::Script(script) => {
@ -210,6 +203,7 @@ fn create_directory(
file: script, file: script,
}, },
task_list: entry.task_list, task_list: entry.task_list,
multi_select,
})); }));
} }
} }

View File

@ -23,7 +23,6 @@ pub enum Command {
pub struct Tab { pub struct Tab {
pub name: String, pub name: String,
pub tree: Tree<Rc<ListNode>>, pub tree: Tree<Rc<ListNode>>,
pub multi_selectable: bool,
} }
#[derive(Clone, Hash, Eq, PartialEq)] #[derive(Clone, Hash, Eq, PartialEq)]
@ -32,4 +31,5 @@ pub struct ListNode {
pub description: String, pub description: String,
pub command: Command, pub command: Command,
pub task_list: String, pub task_list: String,
pub multi_select: bool,
} }

View File

@ -1,5 +1,4 @@
name = "System Setup" name = "System Setup"
multi_selectable = false
[[data]] [[data]]
name = "Arch Linux" name = "Arch Linux"
@ -14,6 +13,7 @@ name = "Arch Server Setup"
description = "This command installs a minimal arch server setup under 5 minutes." description = "This command installs a minimal arch server setup under 5 minutes."
script = "arch/server-setup.sh" script = "arch/server-setup.sh"
task_list = "SI D" task_list = "SI D"
multi_select = false
[[data.entries]] [[data.entries]]
name = "Paru AUR Helper" name = "Paru AUR Helper"
@ -82,12 +82,14 @@ name = "Full System Cleanup"
description = "This script is designed to remove unnecessary packages, clean old cache files, remove temporary files, and to empty the trash." description = "This script is designed to remove unnecessary packages, clean old cache files, remove temporary files, and to empty the trash."
script = "system-cleanup.sh" script = "system-cleanup.sh"
task_list = "RP PFM" task_list = "RP PFM"
multi_select = false
[[data]] [[data]]
name = "Full System Update" name = "Full System Update"
description = "This command updates your system to the latest packages available for your distro" description = "This command updates your system to the latest packages available for your distro"
script = "system-update.sh" script = "system-update.sh"
task_list = "PFM" task_list = "PFM"
multi_select = false
[[data]] [[data]]
name = "Gaming Dependencies" name = "Gaming Dependencies"

View File

@ -1,8 +1,8 @@
name = "Utilities" name = "Utilities"
multi_selectable = false
[[data]] [[data]]
name = "Monitor Control" name = "Monitor Control"
multi_select = false
[[data.preconditions]] [[data.preconditions]]
matches = true matches = true
@ -78,6 +78,7 @@ script = "monitor-control/set_resolutions.sh"
[[data]] [[data]]
name = "User Account Manager" name = "User Account Manager"
multi_select = false
[[data.entries]] [[data.entries]]
name = "Add User" name = "Add User"
@ -104,6 +105,7 @@ name = "Auto Mount Drive"
description = "This utility is designed to help with automating the process of mounting a drive on to your system." description = "This utility is designed to help with automating the process of mounting a drive on to your system."
script = "auto-mount.sh" script = "auto-mount.sh"
task_list = "PFM" task_list = "PFM"
multi_select = false
[[data]] [[data]]
name = "Auto Login" name = "Auto Login"
@ -120,6 +122,7 @@ name = "Bluetooth Manager"
description = "This utility is designed to manage bluetooth in your system" description = "This utility is designed to manage bluetooth in your system"
script = "bluetooth-control.sh" script = "bluetooth-control.sh"
task_list = "I SS" task_list = "I SS"
multi_select = false
[[data]] [[data]]
name = "Bootable USB Creator" name = "Bootable USB Creator"

View File

@ -153,12 +153,10 @@ impl AppState {
hints.push(Shortcut::new("Select item below", ["j", "Down"])); hints.push(Shortcut::new("Select item below", ["j", "Down"]));
hints.push(Shortcut::new("Next theme", ["t"])); hints.push(Shortcut::new("Next theme", ["t"]));
hints.push(Shortcut::new("Previous theme", ["T"])); hints.push(Shortcut::new("Previous theme", ["T"]));
hints.push(Shortcut::new("Multi-selection mode", ["v"]));
if self.is_current_tab_multi_selectable() { if self.multi_select {
hints.push(Shortcut::new("Toggle multi-selection mode", ["v"]));
hints.push(Shortcut::new("Select multiple commands", ["Space"])); hints.push(Shortcut::new("Select multiple commands", ["Space"]));
} }
hints.push(Shortcut::new("Next tab", ["Tab"])); hints.push(Shortcut::new("Next tab", ["Tab"]));
hints.push(Shortcut::new("Previous tab", ["Shift-Tab"])); hints.push(Shortcut::new("Previous tab", ["Shift-Tab"]));
hints.push(Shortcut::new("Important actions guide", ["g"])); hints.push(Shortcut::new("Important actions guide", ["g"]));
@ -330,7 +328,12 @@ impl AppState {
let (indicator, style) = if is_selected { let (indicator, style) = if is_selected {
(self.theme.multi_select_icon(), Style::default().bold()) (self.theme.multi_select_icon(), Style::default().bold())
} else { } else {
("", Style::new()) let ms_style = if self.multi_select && !node.multi_select {
Style::default().fg(self.theme.multi_select_disabled_color())
} else {
Style::new()
};
("", ms_style)
}; };
if *has_children { if *has_children {
Line::from(format!( Line::from(format!(
@ -340,6 +343,7 @@ impl AppState {
indicator indicator
)) ))
.style(self.theme.dir_color()) .style(self.theme.dir_color())
.patch_style(style)
} else { } else {
Line::from(format!( Line::from(format!(
"{} {} {}", "{} {} {}",
@ -357,13 +361,21 @@ impl AppState {
|ListEntry { |ListEntry {
node, has_children, .. node, has_children, ..
}| { }| {
let ms_style = if self.multi_select && !node.multi_select {
Style::default().fg(self.theme.multi_select_disabled_color())
} else {
Style::new()
};
if *has_children { if *has_children {
Line::from(" ").style(self.theme.dir_color()) Line::from(" ")
.style(self.theme.dir_color())
.patch_style(ms_style)
} else { } else {
Line::from(format!("{} ", node.task_list)) Line::from(format!("{} ", node.task_list))
.alignment(Alignment::Right) .alignment(Alignment::Right)
.style(self.theme.cmd_color()) .style(self.theme.cmd_color())
.bold() .bold()
.patch_style(ms_style)
} }
}, },
)); ));
@ -479,6 +491,13 @@ impl AppState {
// enabled, need to clear it to prevent state corruption // enabled, need to clear it to prevent state corruption
if !self.multi_select { if !self.multi_select {
self.selected_commands.clear() self.selected_commands.clear()
} else {
// Prevents non multi_selectable cmd from being pushed into the selected list
if let Some(node) = self.get_selected_node() {
if !node.multi_select {
self.selected_commands.retain(|cmd| cmd.name != node.name);
}
}
} }
} }
ConfirmStatus::Confirm => self.handle_confirm_command(), ConfirmStatus::Confirm => self.handle_confirm_command(),
@ -556,41 +575,31 @@ impl AppState {
} }
fn toggle_multi_select(&mut self) { fn toggle_multi_select(&mut self) {
if self.is_current_tab_multi_selectable() { self.multi_select = !self.multi_select;
self.multi_select = !self.multi_select; if !self.multi_select {
if !self.multi_select { self.selected_commands.clear();
self.selected_commands.clear();
}
} }
} }
fn toggle_selection(&mut self) { fn toggle_selection(&mut self) {
if let Some(command) = self.get_selected_node() { if let Some(node) = self.get_selected_node() {
if self.selected_commands.contains(&command) { if node.multi_select {
self.selected_commands.retain(|c| c != &command); if self.selected_commands.contains(&node) {
} else { self.selected_commands.retain(|c| c != &node);
self.selected_commands.push(command); } else {
self.selected_commands.push(node);
}
} }
} }
} }
pub fn is_current_tab_multi_selectable(&self) -> bool {
let index = self.current_tab.selected().unwrap_or(0);
self.tabs
.get(index)
.map_or(false, |tab| tab.multi_selectable)
}
fn update_items(&mut self) { fn update_items(&mut self) {
self.filter.update_items( self.filter.update_items(
&self.tabs, &self.tabs,
self.current_tab.selected().unwrap(), self.current_tab.selected().unwrap(),
self.visit_stack.last().unwrap().0, self.visit_stack.last().unwrap().0,
); );
if !self.is_current_tab_multi_selectable() {
self.multi_select = false;
self.selected_commands.clear();
}
let len = self.filter.item_list().len(); let len = self.filter.item_list().len();
if len > 0 { if len > 0 {
let current = self.selection.selected().unwrap_or(0); let current = self.selection.selected().unwrap_or(0);

View File

@ -28,6 +28,13 @@ impl Theme {
} }
} }
pub fn multi_select_disabled_color(&self) -> Color {
match self {
Theme::Default => Color::DarkGray,
Theme::Compatible => Color::DarkGray,
}
}
pub fn tab_color(&self) -> Color { pub fn tab_color(&self) -> Color {
match self { match self {
Theme::Default => Color::Rgb(255, 255, 85), Theme::Default => Color::Rgb(255, 255, 85),