128 lines
4.4 KiB
Rust
128 lines
4.4 KiB
Rust
|
|
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<Self> {
|
||
|
|
Ok(Self {
|
||
|
|
state: AppState::load()?,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn handle_event(&mut self, event: Event) -> anyhow::Result<bool> {
|
||
|
|
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<TimeItem> {
|
||
|
|
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(())
|
||
|
|
}
|
||
|
|
}
|