agadir 0.1.0

Blogging over the terminal
Documentation
use std::error;

use chrono::NaiveDate;
use ratatui::{
    layout::{Alignment, Constraint, Direction, Layout},
    style::{Color, Style, Stylize},
    text::Text,
    widgets::{Block, Clear, Padding, Paragraph, Row, Table, TableState, Wrap},
    Frame,
};

use crate::post::Post;

pub type AppResult<T> = std::result::Result<T, Box<dyn error::Error>>;

#[derive(Debug, Clone, PartialEq)]
pub enum FocusedBlock {
    Toc,
    Post,
}

pub struct App<'a> {
    pub running: bool,
    pub focused_block: FocusedBlock,
    pub posts: Vec<Post<'a>>,
    pub scroll: u16,
    pub toc: Vec<(NaiveDate, Text<'a>)>,
    pub toc_state: TableState,
    pub area_height: u16,
    pub previous_key: Vec<u8>,
}

impl<'a> App<'a> {
    pub fn new(posts: Vec<Post<'a>>, toc: Vec<(NaiveDate, Text<'a>)>) -> Self {
        let mut toc_state = TableState::default();
        if toc.is_empty() {
            toc_state.select(None);
        } else {
            toc_state.select(Some(0));
        }

        let focused_block = FocusedBlock::Toc;
        let scroll: u16 = 0;

        let area_height = 0;

        Self {
            running: true,
            focused_block,
            posts,
            scroll,
            toc,
            toc_state,
            area_height,
            previous_key: vec![0],
        }
    }

    pub fn render(&self, frame: &mut Frame) {
        match self.focused_block {
            FocusedBlock::Toc => {
                let layout = Layout::default()
                    .direction(Direction::Vertical)
                    .constraints(
                        [
                            Constraint::Percentage(20),
                            Constraint::Min(self.toc.len() as u16 + 2),
                            Constraint::Percentage(20),
                        ]
                        .as_ref(),
                    )
                    .split(frame.size());

                let block = Layout::default()
                    .direction(Direction::Horizontal)
                    .constraints(
                        [
                            Constraint::Percentage(20),
                            Constraint::Fill(1),
                            Constraint::Percentage(20),
                        ]
                        .as_ref(),
                    )
                    .split(layout[1])[1];

                let widths = [Constraint::Length(20), Constraint::Fill(1)];
                let rows: Vec<Row> = self
                    .toc
                    .iter()
                    .map(|key| {
                        let date = key.0.format("%B %d %Y").to_string();
                        let title = key.1.to_string();
                        Row::new(vec![date, title]).style(Style::default().fg(Color::White))
                    })
                    .collect();

                let table = Table::new(rows, widths)
                    .block(
                        Block::default()
                            .title("Posts")
                            .title_style(Style::default().bold().fg(Color::Green))
                            .title_alignment(Alignment::Center)
                            .padding(Padding::top(2))
                            .style(Style::default()),
                    )
                    .highlight_symbol(Text::from(">>  ").style(Style::new().yellow().bold()));

                frame.render_widget(Clear, block);
                frame.render_stateful_widget(table, block, &mut self.toc_state.clone());
            }

            FocusedBlock::Post => {
                if let Some(i) = self.toc_state.selected() {
                    let post_title = &self.toc[i].1;

                    if let Some(post) = self.posts.iter().find(|p| &p.title == post_title) {
                        let paragraph = Paragraph::new(post.content.clone())
                            .wrap(Wrap { trim: false })
                            .block(Block::new().title_alignment(Alignment::Center))
                            .style(Style::default())
                            .scroll((self.scroll, 0));

                        frame.render_widget(paragraph, frame.size());
                    }
                };
            }
        }
    }
}