memos_cli_ui/
list.rs

1mod detail;
2mod list;
3
4use anyhow::{Error, Result};
5use crossterm::event::{self, KeyCode, KeyEventKind};
6use memos_api::models::V1Memo;
7use ratatui::prelude::*;
8use ratatui::DefaultTerminal;
9use std::cell::RefCell;
10use std::{cmp, io};
11
12use self::detail::Detail;
13use self::list::List;
14
15pub struct ListView<'a> {
16    memos: &'a Vec<V1Memo>,
17    current_index: RefCell<usize>,
18}
19
20impl<'a> ListView<'a> {
21    pub fn try_new(value: &'a Vec<V1Memo>) -> Result<Self> {
22        if value.len() < 1 {
23            return Err(Error::msg("List is empty."));
24        }
25
26        let list_view = ListView {
27            memos: value,
28            current_index: 0.into(),
29        };
30
31        Ok(list_view)
32    }
33
34    pub fn draw(&self) -> io::Result<()> {
35        let mut terminal = ratatui::init();
36
37        terminal.clear()?;
38
39        let result = self.run(terminal);
40
41        ratatui::restore();
42
43        result
44    }
45
46    fn run(&self, mut terminal: DefaultTerminal) -> io::Result<()> {
47        loop {
48            terminal.draw(|frame| {
49                let layout = Layout::default()
50                    .constraints([Constraint::Min(2)])
51                    .horizontal_margin(20)
52                    .vertical_margin(10)
53                    .direction(Direction::Horizontal)
54                    .constraints(vec![Constraint::Percentage(25), Constraint::Percentage(50)])
55                    .split(frame.area());
56
57                let list = List::new(&self);
58                let detail = Detail::new(&self);
59
60                frame.render_widget(list, layout[0]);
61                frame.render_widget(detail, layout[1]);
62            })?;
63
64            if let event::Event::Key(key) = event::read()? {
65                match (key.kind, key.code) {
66                    (KeyEventKind::Press, KeyCode::Char('j')) => {
67                        let current_index = *self.current_index.borrow();
68                        let current_index = current_index.saturating_add(1);
69                        let current_index = cmp::min(self.memos.len() - 1, current_index);
70                        self.current_index.replace(current_index);
71                    }
72                    (KeyEventKind::Press, KeyCode::Char('k')) => {
73                        let current_index = *self.current_index.borrow();
74                        let current_index = current_index.saturating_sub(1);
75                        self.current_index.replace(current_index);
76                    }
77                    (KeyEventKind::Press, KeyCode::Char('q')) => return Ok(()),
78                    _ => (),
79                }
80            }
81        }
82    }
83
84    fn get_current_memo(&self) -> &V1Memo {
85        let current_index = *self.current_index.borrow();
86        &self.memos[current_index]
87    }
88}