1#[allow(clippy::wildcard_imports)]
2use super::*;
3
4impl App {
5 pub(in crate::app) fn cycle_entries(&self) -> Vec<crate::ui::cycle::CycleEntry> {
14 let root = self.project_root();
15 let planner = Planner::from_config(self.store.config.clone(), root);
16 let mut names = planner.cycle_names();
17 if !names
18 .iter()
19 .any(|name| name == crate::cycle_store::DEFAULT_CYCLE_NAME)
20 {
21 names.push(crate::cycle_store::DEFAULT_CYCLE_NAME.to_string());
22 }
23 names
24 .into_iter()
25 .map(|name| {
26 let cycle = planner.cycle(&name);
27 crate::ui::cycle::CycleEntry {
28 progress: planner.cycle_progress(&self.store, &name),
29 description: cycle
30 .and_then(|c| c.description.clone())
31 .unwrap_or_default(),
32 start_date: cycle.and_then(|c| c.start_date.clone()),
33 due_date: cycle.and_then(|c| c.due_date.clone()),
34 name,
35 }
36 })
37 .collect()
38 }
39
40 pub(in crate::app) fn cycle_groups(&self) -> Vec<crate::ui::cycle::CycleGrouping> {
42 crate::ui::cycle::group_entries(self.cycle_entries(), &today())
43 }
44
45 pub fn active_cycle_name(&self) -> Option<String> {
55 if self.top_mode != TopMode::Cycle {
56 return None;
57 }
58 let selected = self.cycle_state.as_ref()?.selected;
59 let groups = self.cycle_groups();
60 crate::ui::cycle::flat_entries(&groups)
61 .get(selected)
62 .map(|entry| entry.name.clone())
63 }
64
65 pub fn active_milestone_name(&self) -> Option<String> {
72 if !self.milestone_mode || self.top_mode != TopMode::Milestones {
73 return None;
74 }
75 let selected = self.planning.as_ref()?.selected;
76 let (_cycles, milestones) = self.planning_rows();
77 milestones.get(selected).map(|row| row.name.clone())
78 }
79
80 pub(in crate::app) fn cycle_entry_count(&self) -> usize {
81 self.cycle_entries().len()
82 }
83
84 pub fn select_cycle_row(&mut self, index: usize) {
86 let len = self.cycle_entry_count();
87 if len == 0 {
88 return;
89 }
90 self.cycle_state
91 .get_or_insert_with(crate::ui::cycle::CycleState::default)
92 .selected = usize::min(index, len - 1);
93 }
94
95 pub(in crate::app) fn handle_cycle_key(&mut self, key: KeyEvent) -> Result<Action> {
99 if !matches!(self.mode, Mode::Cycle) {
100 self.mode = Mode::Board;
101 return Ok(Action::None);
102 }
103 match key.code {
104 KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => {
105 self.cycle_state = None;
106 self.mode = Mode::Board;
107 }
108 KeyCode::Up | KeyCode::Char('k') => {
109 let len = self.cycle_entry_count();
110 if let Some(state) = self.cycle_state.as_mut() {
111 state.move_selected(-1, len);
112 }
113 }
114 KeyCode::Down | KeyCode::Char('j') => {
115 let len = self.cycle_entry_count();
116 if let Some(state) = self.cycle_state.as_mut() {
117 state.move_selected(1, len);
118 }
119 }
120 KeyCode::Left | KeyCode::Char('h') => {
126 let groups = self.cycle_groups();
127 if let Some(state) = self.cycle_state.as_mut() {
128 state.move_group(&groups, -1);
129 }
130 }
131 KeyCode::Right | KeyCode::Char('l') => {
132 let groups = self.cycle_groups();
133 if let Some(state) = self.cycle_state.as_mut() {
134 state.move_group(&groups, 1);
135 }
136 }
137 KeyCode::Char('m') | KeyCode::Char('M') => self.open_planning_modal(),
138 KeyCode::Char('?') => self.open_help(Mode::Cycle),
139 _ => {}
140 }
141 Ok(Action::None)
142 }
143
144 pub(in crate::app) fn handle_repos_key(&mut self, key: KeyEvent) -> Result<Action> {
145 if !matches!(self.mode, Mode::Repos) {
146 return Ok(Action::None);
147 }
148 let entries = self.repo_entries();
149 let len = entries.len();
150 if len > 0 {
151 self.repos_cursor = self.repos_cursor.min(len - 1);
152 } else {
153 self.repos_cursor = 0;
154 }
155 match key.code {
156 KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => {
157 self.mode = Mode::Board;
158 }
159 KeyCode::Up | KeyCode::Char('k') => {
160 self.repos_cursor = self.repos_cursor.saturating_sub(1);
161 }
162 KeyCode::Down | KeyCode::Char('j') => {
163 if len > 0 && self.repos_cursor + 1 < len {
164 self.repos_cursor += 1;
165 }
166 }
167 KeyCode::Enter => {
168 if let Some((_, path, _)) = entries.get(self.repos_cursor) {
169 return Ok(Action::OpenRepo(path.clone()));
170 }
171 }
172 KeyCode::Char('?') => self.open_help(Mode::Repos),
173 _ => {}
174 }
175 Ok(Action::None)
176 }
177}