1use crossterm::event::{KeyCode, KeyEvent};
2use ratatui::{
3 Frame,
4 layout::{Alignment, Constraint, HorizontalAlignment::Center, Layout, Rect},
5 style::{Stylize, palette::material::WHITE},
6 text::{Line, Text},
7 widgets::{Block, Padding, Paragraph, Wrap},
8};
9
10use crate::{
11 git::{kit::KitRepo, model::KitCommit, status::KitStatus, util},
12 tui::{ACCENT, ACCENT_TEXT, GITKIT_ASCII, Renderable},
13};
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
16pub enum Page {
17 #[default]
18 Home = 0,
19 Cadence = 1,
20 Silo = 2,
21}
22
23impl Page {
24 pub const ALL: [Page; 3] = [Page::Home, Page::Cadence, Page::Silo];
25
26 pub fn to_str(&self) -> &'static str {
27 match self {
28 Page::Home => "Home",
29 Page::Cadence => "Cadence",
30 Page::Silo => "Silo",
31 }
32 }
33
34 pub fn size() -> usize {
35 Self::ALL.len()
36 }
37
38 pub fn next(&self) -> Page {
39 match &self {
40 Page::Home => Page::Cadence,
41 Page::Cadence => Page::Silo,
42 Page::Silo => Page::Home,
43 }
44 }
45}
46
47pub struct HomeData {
48 pub repo_name: String, pub current_branch: String,
50 pub total_commits: u32,
51 pub status: KitStatus,
52 pub first_commit: Option<KitCommit>,
53 pub last_commit: Option<KitCommit>,
54}
55
56impl HomeData {
57 pub fn new(repo: &KitRepo) -> Self {
58 let workdir = repo.inner.workdir().unwrap_or_else(|| repo.inner.path());
59
60 let repo_name = workdir
61 .file_name()
62 .map(|n| n.to_string_lossy().to_string())
63 .unwrap_or_else(|| "unkown workdir".to_string());
64 let current_branch = repo
65 .current_branch()
66 .unwrap_or_else(|_| "not found".to_owned());
67
68 let total_commits: u32 = repo
69 .iter_commits()
70 .map_or(0, |iter| iter.count())
71 .try_into()
72 .unwrap_or(u32::MAX);
73
74 let status = repo.get_status();
75
76 let first_commit: Option<KitCommit> =
78 repo.iter_commits().map_or(None, |commits| commits.last());
79
80 let last_commit = repo
81 .iter_commits()
82 .map_or(None, |mut commits| commits.next());
83
84 HomeData {
85 repo_name,
86 current_branch,
87 total_commits,
88 status,
89 first_commit,
90 last_commit,
91 }
92 }
93}
94
95pub struct HomePage {
96 data: HomeData,
97}
98
99impl HomePage {
100 pub fn new(data: HomeData) -> Self {
101 HomePage { data }
102 }
103
104 pub fn handle_key(&mut self, key_event: KeyEvent, _repo: &KitRepo, refresh: &mut bool) {
105 match key_event.code {
106 KeyCode::Char('r') => *refresh = true,
107 _ => {}
108 }
109 }
110
111 fn info_box(&self, frame: &mut Frame, area: Rect) {
113 let title = Line::from(vec![
114 "|<".into(),
115 "Repository: ".fg(ACCENT_TEXT).bold(),
116 format!("{}", self.data.repo_name).fg(ACCENT),
117 ">|".into(),
118 ]);
119 let general = Block::bordered()
120 .title(title)
121 .title_alignment(Center)
122 .padding(Padding {
123 left: 2,
124 right: 0,
125 top: 1,
126 bottom: 0,
127 });
128
129 let active_since = util::active_since(&self.data.first_commit);
130 let last_activity = util::last_activity(&self.data.last_commit);
131
132 let mut info = vec![
133 Line::from(vec![
134 "branch: ".fg(ACCENT).bold(),
135 format!("{}", self.data.current_branch).fg(WHITE),
136 ]),
137 Line::from(vec![
138 "total commits: ".fg(ACCENT).bold(),
139 format!("{}", self.data.total_commits).fg(WHITE),
140 ]),
141 active_since,
142 last_activity,
143 ];
144
145 let status = &self.data.status;
146
147 info.push(Line::from("status: ".fg(ACCENT).bold()));
148 info.extend(status.tui_print());
149
150 let min_height = info.len() as u16 + (info.len() as u16) / 2 + 3;
153
154 let vertical_chunks =
155 Layout::vertical([Constraint::Length(min_height), Constraint::Min(0)]).split(area);
156
157 let info_area = vertical_chunks[0].centered_horizontally(Constraint::Percentage(50));
158
159 let paragraph = Paragraph::new(info)
160 .block(general)
161 .alignment(Alignment::Left)
162 .wrap(Wrap { trim: true });
163
164 frame.render_widget(paragraph, info_area);
165 }
166}
167
168impl<'repo> Renderable for HomePage {
169 fn render(&mut self, frame: &mut ratatui::prelude::Frame, area: ratatui::prelude::Rect) {
170 let header_height = GITKIT_ASCII.lines().count() as u16;
171
172 let subtext_height = 3;
173 let chunks = Layout::vertical([
174 Constraint::Length(header_height),
175 Constraint::Length(1), Constraint::Length(subtext_height),
177 Constraint::Length(2), Constraint::Min(0), ])
180 .split(area);
181
182 let header = Text::from(GITKIT_ASCII).alignment(Center).style(WHITE);
183 let sub_text = Text::from(format!(
184 "gitkit version {}\n made by {} \nrepo: {}",
185 env!("CARGO_PKG_VERSION"),
186 env!("CARGO_PKG_AUTHORS"),
187 env!("CARGO_PKG_REPOSITORY")
188 ))
189 .style(ACCENT_TEXT)
190 .italic()
191 .alignment(Center);
192
193 frame.render_widget(header, chunks[0]);
194 frame.render_widget(sub_text, chunks[2]);
195 self.info_box(frame, chunks[4]);
196 }
197}