use std::process::Command; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use crate::state::{AppState, TimeItem}; pub struct App { pub state: AppState, } impl App { pub fn new() -> anyhow::Result { Ok(Self { state: AppState::load()?, }) } pub fn handle_event(&mut self, event: Event) -> anyhow::Result { match event { Event::Key(KeyEvent { code, modifiers, .. }) => match (code, modifiers) { (KeyCode::Char('q'), _) => return Ok(true), (KeyCode::Char('j'), _) | (KeyCode::Down, _) => self.move_selection(1), (KeyCode::Char('k'), _) | (KeyCode::Up, _) => self.move_selection(-1), (KeyCode::Char('h'), _) | (KeyCode::Left, _) => self.change_pane(-1), (KeyCode::Char('l'), _) | (KeyCode::Right, _) => self.change_pane(1), (KeyCode::Enter, _) => self.toggle_current_item()?, (KeyCode::Char('e'), KeyModifiers::CONTROL) => self.edit_config()?, _ => {} }, _ => {} } Ok(false) } fn move_selection(&mut self, delta: i32) { let items_len = match self.state.current_pane { 0 => self.state.permanent_items.len(), 1 => self.state.recurring_items.len(), 2 => self.state.recent_items.len(), _ => return, }; if items_len == 0 { return; } let current = self.state.selected_indices[self.state.current_pane] as i32; let new_index = (current + delta).rem_euclid(items_len as i32) as usize; self.state.selected_indices[self.state.current_pane] = new_index; } fn change_pane(&mut self, delta: i32) { self.state.current_pane = ((self.state.current_pane as i32 + delta).rem_euclid(3)) as usize; } fn get_current_item(&self) -> Option { let items = match self.state.current_pane { 0 => &self.state.permanent_items, 1 => &self.state.recurring_items, 2 => &self.state.recent_items, _ => return None, }; let index = self.state.selected_indices[self.state.current_pane]; items.get(index).cloned() } fn toggle_current_item(&mut self) -> anyhow::Result<()> { if let Some(item) = self.get_current_item() { if self.state.active_timer.as_ref().map(|(active, _)| active.name == item.name).unwrap_or(false) { self.state.stop_timer()?; } else { self.state.start_timer(item)?; } self.state.save()?; } Ok(()) } fn edit_config(&self) -> anyhow::Result<()> { let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string()); let config_path = AppState::config_file()?; if !config_path.exists() { std::fs::write(&config_path, "# WAT Configuration\n# Add your permanent items here\n\npermanent_items:\n - name: Daily Standup\n tags: [daily, meeting]\n")?; } Command::new(editor) .arg(&config_path) .spawn()? .wait()?; // Reload configuration if config_path.exists() { let contents = std::fs::read_to_string(config_path)?; let config: serde_yaml::Value = serde_yaml::from_str(&contents)?; if let Some(items) = config["permanent_items"].as_sequence() { // Clear existing permanent items self.state.permanent_items.clear(); // Add new items from config for item in items { if let (Some(name), Some(tags)) = ( item["name"].as_str(), item["tags"].as_sequence() ) { let tags = tags .iter() .filter_map(|t| t.as_str()) .map(String::from) .collect(); self.state.permanent_items.push(TimeItem { name: name.to_string(), tags, last_used: None, }); } } } } Ok(()) } }