Skip to main content

binocular/preview/
doc.rs

1use ratatui::style::{Color, Modifier, Style};
2use ratatui::text::{Line, Span, Text};
3
4pub struct PreviewDoc {
5    lines: Vec<Line<'static>>,
6}
7
8impl PreviewDoc {
9    pub fn new() -> Self {
10        Self { lines: Vec::new() }
11    }
12
13    pub fn push_line(&mut self, line: Line<'static>) {
14        self.lines.push(line);
15    }
16
17    pub fn push_section(&mut self, title: &'static str) {
18        self.lines.push(Line::from(vec![Span::styled(
19            title,
20            Style::default()
21                .fg(Color::Yellow)
22                .add_modifier(Modifier::BOLD),
23        )]));
24    }
25
26    pub fn push_field<T: Into<String>>(&mut self, label: &str, value: T, value_color: Color) {
27        self.lines.push(Line::from(vec![
28            Span::styled(
29                format!("   {}: ", label),
30                Style::default().fg(Color::DarkGray),
31            ),
32            Span::styled(value.into(), Style::default().fg(value_color)),
33        ]));
34    }
35
36    pub fn push_indexed(&mut self, index: usize, text: String) {
37        self.lines.push(Line::from(vec![
38            Span::styled(
39                format!("   {:2}: ", index),
40                Style::default().fg(Color::DarkGray),
41            ),
42            Span::styled(text, Style::default().fg(Color::White)),
43        ]));
44    }
45
46    pub fn push_muted_italic<T: Into<String>>(&mut self, text: T) {
47        self.lines.push(Line::from(vec![Span::styled(
48            text.into(),
49            Style::default()
50                .fg(Color::DarkGray)
51                .add_modifier(Modifier::ITALIC),
52        )]));
53    }
54
55    pub fn push_blank_line(&mut self) {
56        self.lines.push(Line::from(""));
57    }
58
59    pub fn into_text(self) -> Text<'static> {
60        Text::from(self.lines)
61    }
62}
63
64pub fn format_file_size(size: u64) -> String {
65    const KB: u64 = 1024;
66    const MB: u64 = KB * 1024;
67    const GB: u64 = MB * 1024;
68
69    if size >= GB {
70        format!("{:.2} GB ({} bytes)", size as f64 / GB as f64, size)
71    } else if size >= MB {
72        format!("{:.2} MB ({} bytes)", size as f64 / MB as f64, size)
73    } else if size >= KB {
74        format!("{:.2} KB ({} bytes)", size as f64 / KB as f64, size)
75    } else {
76        format!("{} bytes", size)
77    }
78}
79
80pub fn format_unix_timestamp(secs: u64) -> String {
81    let days_since_epoch = secs / 86400;
82    let time_of_day = secs % 86400;
83    let hours = time_of_day / 3600;
84    let minutes = (time_of_day % 3600) / 60;
85
86    let mut year = 1970;
87    let mut remaining_days = days_since_epoch as i64;
88
89    loop {
90        let days_in_year = if is_leap_year(year) { 366 } else { 365 };
91        if remaining_days < days_in_year {
92            break;
93        }
94        remaining_days -= days_in_year;
95        year += 1;
96    }
97
98    let days_in_months: [i64; 12] = if is_leap_year(year) {
99        [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
100    } else {
101        [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
102    };
103
104    let mut month = 1;
105    for days in &days_in_months {
106        if remaining_days < *days {
107            break;
108        }
109        remaining_days -= *days;
110        month += 1;
111    }
112
113    let day = remaining_days + 1;
114    format!(
115        "{:04}-{:02}-{:02} {:02}:{:02}",
116        year, month, day, hours, minutes
117    )
118}
119
120fn is_leap_year(year: i64) -> bool {
121    (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
122}