Skip to main content

flow_tui/
app.rs

1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
2use flow_core::{Feature, Theme};
3use ratatui::Frame;
4
5use crate::theme::TuiTheme;
6use crate::views;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum View {
10    Kanban,
11    Agents,
12    Logs,
13    Graph,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum LayoutMode {
18    Full,
19    Compact,
20    Mobile,
21}
22
23pub struct App {
24    pub current_view: View,
25    pub layout_mode: LayoutMode,
26    pub theme: Theme,
27    pub tui_theme: TuiTheme,
28    pub features: Vec<Feature>,
29    pub selected_index: usize,
30    pub show_help: bool,
31    #[allow(dead_code)]
32    pub should_quit: bool,
33    pub terminal_width: u16,
34    pub terminal_height: u16,
35    #[allow(dead_code)]
36    pub scroll_offset: usize,
37    pub log_messages: Vec<String>,
38}
39
40impl App {
41    pub fn new() -> Self {
42        let theme = Theme::default();
43        let tui_theme = TuiTheme::from(&theme.colors());
44
45        let mut app = Self {
46            current_view: View::Kanban,
47            layout_mode: LayoutMode::Full,
48            theme,
49            tui_theme,
50            features: Vec::new(),
51            selected_index: 0,
52            show_help: false,
53            should_quit: false,
54            terminal_width: 120,
55            terminal_height: 30,
56            scroll_offset: 0,
57            log_messages: Vec::new(),
58        };
59
60        app.load_demo_data();
61        app
62    }
63
64    pub fn render(&self, frame: &mut Frame) {
65        if self.show_help {
66            views::help::render(frame, self);
67        } else {
68            match self.current_view {
69                View::Kanban => views::kanban::render(frame, self),
70                View::Agents => views::agents::render(frame, self),
71                View::Logs => views::logs::render(frame, self),
72                View::Graph => views::graph::render(frame, self),
73            }
74        }
75    }
76
77    pub fn handle_key(&mut self, key: KeyEvent) -> bool {
78        // Quit commands
79        if key.code == KeyCode::Char('q')
80            || (key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL))
81        {
82            return true;
83        }
84
85        // Help toggle
86        if key.code == KeyCode::Char('?') {
87            self.show_help = !self.show_help;
88            return false;
89        }
90
91        // If help is showing, only toggle it off
92        if self.show_help {
93            self.show_help = false;
94            return false;
95        }
96
97        // View switching
98        match key.code {
99            KeyCode::Char('1') => self.current_view = View::Kanban,
100            KeyCode::Char('2') => self.current_view = View::Agents,
101            KeyCode::Char('3') => self.current_view = View::Logs,
102            KeyCode::Char('4') => self.current_view = View::Graph,
103            KeyCode::Tab => self.next_view(),
104            _ => {}
105        }
106
107        // Navigation
108        match key.code {
109            KeyCode::Char('j') | KeyCode::Down => self.next_item(),
110            KeyCode::Char('k') | KeyCode::Up => self.prev_item(),
111            _ => {}
112        }
113
114        // Theme cycling
115        if key.code == KeyCode::Char('t') {
116            self.cycle_theme();
117        }
118
119        false
120    }
121
122    pub fn resize(&mut self, width: u16, height: u16) {
123        self.terminal_width = width;
124        self.terminal_height = height;
125
126        // Update layout mode based on width
127        self.layout_mode = if width >= 120 {
128            LayoutMode::Full
129        } else if width >= 80 {
130            LayoutMode::Compact
131        } else {
132            LayoutMode::Mobile
133        };
134    }
135
136    fn next_view(&mut self) {
137        self.current_view = match self.current_view {
138            View::Kanban => View::Agents,
139            View::Agents => View::Logs,
140            View::Logs => View::Graph,
141            View::Graph => View::Kanban,
142        };
143    }
144
145    fn next_item(&mut self) {
146        if !self.features.is_empty() {
147            self.selected_index = (self.selected_index + 1) % self.features.len();
148        }
149    }
150
151    fn prev_item(&mut self) {
152        if !self.features.is_empty() {
153            self.selected_index = if self.selected_index == 0 {
154                self.features.len() - 1
155            } else {
156                self.selected_index - 1
157            };
158        }
159    }
160
161    fn cycle_theme(&mut self) {
162        self.theme = self.theme.next();
163        self.tui_theme = TuiTheme::from(&self.theme.colors());
164        self.add_log(format!("Theme changed to: {}", self.theme.name()));
165    }
166
167    pub fn add_log(&mut self, message: String) {
168        self.log_messages.push(message);
169        // Keep last 100 messages
170        if self.log_messages.len() > 100 {
171            self.log_messages.remove(0);
172        }
173    }
174
175    pub fn load_demo_data(&mut self) {
176        // Create demo features for testing
177        self.features = vec![
178            Feature {
179                id: 1,
180                priority: 100,
181                category: "Backend".to_string(),
182                name: "User Authentication".to_string(),
183                description: "Implement JWT-based authentication system".to_string(),
184                steps: vec![
185                    "Create user model".to_string(),
186                    "Implement JWT tokens".to_string(),
187                    "Add login/logout endpoints".to_string(),
188                ],
189                passes: false,
190                in_progress: false,
191                dependencies: vec![],
192                created_at: Some("2024-01-15T10:00:00Z".to_string()),
193                updated_at: Some("2024-01-15T10:00:00Z".to_string()),
194            },
195            Feature {
196                id: 2,
197                priority: 90,
198                category: "Frontend".to_string(),
199                name: "Login Page UI".to_string(),
200                description: "Create responsive login page".to_string(),
201                steps: vec![
202                    "Design mockup".to_string(),
203                    "Implement form".to_string(),
204                    "Add validation".to_string(),
205                ],
206                passes: false,
207                in_progress: false,
208                dependencies: vec![],
209                created_at: Some("2024-01-15T11:00:00Z".to_string()),
210                updated_at: Some("2024-01-15T11:00:00Z".to_string()),
211            },
212            Feature {
213                id: 3,
214                priority: 80,
215                category: "Backend".to_string(),
216                name: "API Rate Limiting".to_string(),
217                description: "Add rate limiting middleware".to_string(),
218                steps: vec!["Research solutions".to_string(), "Implement middleware".to_string()],
219                passes: false,
220                in_progress: true,
221                dependencies: vec![1],
222                created_at: Some("2024-01-16T09:00:00Z".to_string()),
223                updated_at: Some("2024-01-16T14:30:00Z".to_string()),
224            },
225            Feature {
226                id: 4,
227                priority: 70,
228                category: "Testing".to_string(),
229                name: "Integration Tests".to_string(),
230                description: "Write comprehensive test suite".to_string(),
231                steps: vec![
232                    "Setup test framework".to_string(),
233                    "Write API tests".to_string(),
234                    "Add CI/CD integration".to_string(),
235                ],
236                passes: false,
237                in_progress: true,
238                dependencies: vec![1, 3],
239                created_at: Some("2024-01-16T10:00:00Z".to_string()),
240                updated_at: Some("2024-01-16T15:00:00Z".to_string()),
241            },
242            Feature {
243                id: 5,
244                priority: 60,
245                category: "Frontend".to_string(),
246                name: "Dashboard".to_string(),
247                description: "User dashboard with stats".to_string(),
248                steps: vec![
249                    "Create layout".to_string(),
250                    "Add charts".to_string(),
251                    "Implement data fetching".to_string(),
252                ],
253                passes: true,
254                in_progress: false,
255                dependencies: vec![1, 2],
256                created_at: Some("2024-01-14T08:00:00Z".to_string()),
257                updated_at: Some("2024-01-15T17:00:00Z".to_string()),
258            },
259            Feature {
260                id: 6,
261                priority: 50,
262                category: "Backend".to_string(),
263                name: "Email Notifications".to_string(),
264                description: "Send email notifications for events".to_string(),
265                steps: vec!["Setup email service".to_string(), "Create templates".to_string()],
266                passes: true,
267                in_progress: false,
268                dependencies: vec![1],
269                created_at: Some("2024-01-13T14:00:00Z".to_string()),
270                updated_at: Some("2024-01-14T16:00:00Z".to_string()),
271            },
272            Feature {
273                id: 7,
274                priority: 40,
275                category: "DevOps".to_string(),
276                name: "Docker Setup".to_string(),
277                description: "Containerize application".to_string(),
278                steps: vec!["Create Dockerfile".to_string(), "Setup docker-compose".to_string()],
279                passes: true,
280                in_progress: false,
281                dependencies: vec![],
282                created_at: Some("2024-01-12T09:00:00Z".to_string()),
283                updated_at: Some("2024-01-13T11:00:00Z".to_string()),
284            },
285            Feature {
286                id: 8,
287                priority: 30,
288                category: "Frontend".to_string(),
289                name: "Settings Page".to_string(),
290                description: "User settings and preferences".to_string(),
291                steps: vec!["Design UI".to_string(), "Implement form handling".to_string()],
292                passes: true,
293                in_progress: false,
294                dependencies: vec![1, 2],
295                created_at: Some("2024-01-11T10:00:00Z".to_string()),
296                updated_at: Some("2024-01-12T15:00:00Z".to_string()),
297            },
298            Feature {
299                id: 9,
300                priority: 20,
301                category: "Documentation".to_string(),
302                name: "API Documentation".to_string(),
303                description: "Complete OpenAPI specs".to_string(),
304                steps: vec!["Write specs".to_string(), "Generate docs".to_string()],
305                passes: true,
306                in_progress: false,
307                dependencies: vec![1, 3],
308                created_at: Some("2024-01-10T13:00:00Z".to_string()),
309                updated_at: Some("2024-01-11T16:00:00Z".to_string()),
310            },
311            Feature {
312                id: 10,
313                priority: 10,
314                category: "Security".to_string(),
315                name: "Security Audit".to_string(),
316                description: "Comprehensive security review".to_string(),
317                steps: vec![
318                    "Run vulnerability scan".to_string(),
319                    "Review dependencies".to_string(),
320                    "Fix issues".to_string(),
321                ],
322                passes: true,
323                in_progress: false,
324                dependencies: vec![1, 3, 6],
325                created_at: Some("2024-01-09T08:00:00Z".to_string()),
326                updated_at: Some("2024-01-10T18:00:00Z".to_string()),
327            },
328        ];
329
330        self.add_log("Loaded 10 demo features".to_string());
331        self.add_log(format!("Current theme: {}", self.theme.name()));
332        self.add_log("Press ? for help".to_string());
333    }
334}