1use std::collections::HashMap;
2use std::path::PathBuf;
3
4use log::info;
5use ratatui::layout::Rect;
6
7use super::state::{
8 App, AppMode, DescriptionState, FocusedPanel, HelpState, MultiSelectState, PreviewState,
9 SearchState, StatefulList,
10};
11use crate::ui::state::{ScriptItem, UiOptions};
12use crate::ui::theme::Theme;
13
14impl<'a> App<'a> {
15 pub fn new(options: &UiOptions) -> App<'a> {
16 let theme = match options.theme.as_str() {
17 "catppuccin-mocha" => Theme::catppuccin_mocha(),
18 "dracula" => Theme::dracula(),
19 "gruvbox" => Theme::gruvbox(),
20 "nord" => Theme::nord(),
21 "rose-pine" => Theme::rose_pine(),
22 _ => Theme::catppuccin_mocha(),
23 };
24
25 App {
26 mode: if options.is_root { AppMode::RootWarning } else { AppMode::Normal },
27 quit: false,
28 focused_panel: FocusedPanel::Categories,
29 log_mode: false,
30 modules_dir: PathBuf::new(),
31 theme,
32 theme_locked: options.theme_locked,
33
34 scripts: StatefulList::new(),
35 categories: StatefulList::new(),
36 all_scripts: HashMap::new(),
37
38 script_panel_area: Rect::default(),
39 preview: PreviewState::default(),
40 search: SearchState::default(),
41 multi_select: MultiSelectState::default(),
42 help: HelpState::default(),
43 description: DescriptionState::default(),
44 run_script_popup: None,
45 script_execution_queue: Vec::new(),
46 }
47 }
48
49 pub fn cycle_theme(&mut self) {
50 self.theme = match self.theme.name.as_str() {
51 "Catppuccin Mocha" => Theme::dracula(),
52 "Dracula" => Theme::gruvbox(),
53 "Gruvbox" => Theme::nord(),
54 "Nord" => Theme::rose_pine(),
55 "Rosé Pine" => Theme::catppuccin_mocha(),
56 _ => Theme::catppuccin_mocha(),
57 }
58 }
59
60 pub fn toggle_description_popup(&mut self) {
61 if self.mode == AppMode::Description {
62 self.mode = AppMode::Normal;
63 self.description.content = None;
64 if self.log_mode {
65 info!("Closed description popup");
66 }
67 } else if let Some(selected_script) = self.get_selected_script() {
68 let desc_path = self.modules_dir.join(&selected_script.category).join("desc.toml");
69
70 if self.log_mode {
71 info!(
72 "Attempting to show description for script: {}/{}",
73 selected_script.category, selected_script.name
74 );
75 info!("Description file path: {}", desc_path.display());
76 }
77
78 if desc_path.exists() {
79 if let Ok(content) = std::fs::read_to_string(&desc_path) {
80 if let Ok(table) = content.parse::<toml::Table>() {
81 let script_path = PathBuf::from(&selected_script.name);
82 let script_name_without_ext =
83 script_path.file_stem().and_then(|s| s.to_str());
84
85 if let Some(name) = script_name_without_ext {
86 if let Some(desc) = table
87 .get(name)
88 .and_then(|v| v.as_table())
89 .and_then(|t| t.get("description"))
90 .and_then(|v| v.as_str())
91 {
92 self.description.content = Some(desc.to_string());
93 self.mode = AppMode::Description;
94 if self.log_mode {
95 info!(
96 "Successfully loaded description and entered description mode."
97 );
98 }
99 } else if self.log_mode {
100 info!(
101 "No description found for script '{}' in desc.toml",
102 selected_script.name
103 );
104 }
105 }
106 } else if self.log_mode {
107 info!("Failed to parse desc.toml at {}", desc_path.display());
108 }
109 } else if self.log_mode {
110 info!("Failed to read desc.toml at {}", desc_path.display());
111 }
112 } else if self.log_mode {
113 info!("desc.toml not found at {}", desc_path.display());
114 }
115 }
116 }
117
118 pub fn get_selected_script(&self) -> Option<&ScriptItem> {
119 self.scripts.state.selected().map(|i| &self.scripts.items[i])
120 }
121}