mj 0.4.3

My Journal - personal tool to capture ideas, work with journals, notes and tasks in your favourite text $EDITOR.
Documentation
use super::*;

pub fn render(term: &mut Term, vault: &Vault, state: &State) -> Result<()> {
  match state {
    State::SearchIdeas(query) => render_search_screen(term, vault, &query, 0, false),
    State::SearchJournals(query) => render_search_screen(term, vault, &query, 1, false),
    State::SearchNotes(query) => render_search_screen(term, vault, &query, 2, false),
    State::SearchTasks(query) => render_search_screen(term, vault, &query, 3, true),
    State::HelpScreen => render_help_screen(term, vault),
  }
}

fn draw_tabs<B>(mut f: &mut Frame<B>, index: usize, vault: &Vault)
where
  B: Backend,
{
  let size = f.size();
  Tabs::default()
    .block(Block::default().borders(Borders::ALL).border_style(Style::default().fg(Color::White)))
    .titles(&["Ideas", "Journals", "Notes", "Tasks", "Help"])
    .style(Style::default().fg(Color::White))
    .highlight_style(Style::default().fg(Color::LightYellow).modifier(Modifier::BOLD))
    .divider(tui::symbols::DOT)
    .select(index)
    .render(&mut f, size);

  let chunks = Layout::default()
    .direction(Direction::Vertical)
    .constraints([Constraint::Length(1), Constraint::Length(1), Constraint::Percentage(100)].as_ref())
    .split(f.size());

  let chunks = Layout::default()
    .direction(Direction::Horizontal)
    .constraints([Constraint::Length(43), Constraint::Min(40), Constraint::Length(2)].as_ref())
    .split(chunks[1]);

  let root = vault.root.as_path().to_path_buf()
    .into_os_string().into_string().unwrap_or(String::from(""));

  Paragraph::new([Text::raw(root)].iter())
    .style(Style::default().fg(Color::DarkGray))
    .alignment(Alignment::Left)
    .wrap(true)
    .render(&mut f, chunks[1]);
}

fn draw_search_prompt<B>(mut f: &mut Frame<B>, query: &Query)
where
  B: Backend,
{
  let chunks = Layout::default()
    .direction(Direction::Vertical)
    .constraints([Constraint::Length(3), Constraint::Length(1), Constraint::Percentage(100)].as_ref())
    .split(f.size());

  let chunks = Layout::default()
    .direction(Direction::Horizontal)
    .constraints([Constraint::Length(9), Constraint::Min(40), Constraint::Length(2)].as_ref())
    .split(chunks[1]);

  Paragraph::new([Text::raw("Input: ")].iter())
    .style(Style::default().fg(Color::Red))
    .alignment(Alignment::Right)
    .render(&mut f, chunks[0]);

  let input = format!("{}_", query.input);
  Paragraph::new([Text::raw(input.as_str())].iter())
    .style(Style::default().fg(Color::Cyan).bg(Color::Black))
    .alignment(Alignment::Left)
    .wrap(true)
    .render(&mut f, chunks[1]);
}

fn format_item(config: &Config, category: &String, name: &String, max_length: Option<usize>, show_tasks: bool) -> String {
  if name == "" {
    category.to_owned()
  } else {
    let (delimiter, name) =
      if !show_tasks {
        (&config.view_delimiter, name.to_owned())
      } else {
        if name.as_str().starts_with(config.task_not_done_prefix.as_str()) {
          let value = name.as_str()
            .trim_start_matches(config.task_not_done_prefix.as_str())
            .trim_start()
            .to_owned();
          (&config.task_not_done_display_prefix, value)
        } else if name.as_str().starts_with(config.task_in_progress_prefix.as_str()) {
          let value = name.as_str()
            .trim_start_matches(config.task_in_progress_prefix.as_str())
            .trim_start()
            .to_owned();
          (&config.task_in_progress_display_prefix, value)
        } else if name.as_str().starts_with(config.task_done_prefix.as_str()) {
          let value = name.as_str()
            .trim_start_matches(config.task_done_prefix.as_str())
            .trim_start()
            .to_owned();
          (&config.task_done_display_prefix, value)
        } else {
          (&config.view_delimiter, name.to_owned())
        }
      };
    if let Some(width) = max_length {
      format!("{:>width$}  {}  {}", category, delimiter, name, width = width)
    } else {
      format!("{}  {}  {}", category, delimiter, name)
    }
  }
}

fn draw_search_results<B>(mut f: &mut Frame<B>, query: &Query, vault: &Vault, show_tasks: bool)
where
  B: Backend,
{
  let chunks = Layout::default()
    .direction(Direction::Vertical)
    .constraints([Constraint::Length(4), Constraint::Min(10), Constraint::Length(0)].as_ref())
    .split(f.size());

  let chunks = Layout::default()
    .direction(Direction::Horizontal)
    .constraints([Constraint::Length(1), Constraint::Min(40), Constraint::Length(1)].as_ref())
    .split(chunks[1]);

  let mut block = Block::default()
    .borders(Borders::ALL)
    .border_style(Style::default().fg(Color::DarkGray));

  let results = query.flatten_results();
  let max_length = results.iter().map(|(a, _, _, _)| a.len()).max();
  let results = results.iter()
    .map(|(a, b, _, _)| format_item(&vault.config, a, b, max_length, show_tasks))
    .collect::<Vec<String>>();

  let hi_color =
    match query.deletion_stage {
      DeletionStage::NotFlagged => Color::LightYellow,
      DeletionStage::Flagged => Color::Red,
    };
  if !results.is_empty() {
    SelectableList::default()
      .block(block)
      .items(results.as_slice())
      .select(query.selected_result_index)
      .style(Style::default().fg(Color::Gray))
      .highlight_style(Style::default().fg(hi_color))
      .highlight_symbol(">>")
      .render(&mut f, chunks[1]);
  } else {
    block.render(&mut f, chunks[1]);
  }
}

fn render_search_screen(term: &mut Term, vault: &Vault, query: &Query, index: usize, show_tasks: bool) -> Result<()> {
  term.draw(|mut f| {
    draw_tabs(&mut f, index, vault);
    draw_search_prompt(&mut f, query);
    draw_search_results(&mut f, query, vault, show_tasks);
  })?;
  Ok(())
}

fn render_help_screen(term: &mut Term, vault: &Vault) -> Result<()> {
  term.draw(|mut f| {
    draw_tabs(&mut f, 4, vault);

    let chunks = Layout::default()
      .direction(Direction::Horizontal)
      .constraints([Constraint::Length(4), Constraint::Percentage(100)].as_ref())
      .split(f.size());

    let chunks = Layout::default()
      .direction(Direction::Vertical)
      .constraints([Constraint::Length(4), Constraint::Length(6), Constraint::Percentage(100)].as_ref())
      .split(chunks[1]);

    let hi_style = Style::default().fg(Color::Red);
    let text = [
      Text::styled("My Journal ", hi_style),
      Text::raw("is a productivity tool that will help you manage your daily ideas,\n"),
      Text::raw("journals, notes and tasks. "),
      Text::styled("My Journal ", hi_style),
      Text::raw("is nothing else but a thin layer atop your\n"),
      Text::raw("default editor that it will use to organize your textual notes in a clean and\n"),
      Text::raw("yet open structure of directories.\n"),
    ];
    Paragraph::new(text.iter())
      .alignment(Alignment::Left)
      .wrap(true)
      .render(&mut f, chunks[1]);

    Block::default()
      .title(" Key bindings ")
      .title_style(Style::default().fg(Color::Yellow))
      .borders(Borders::NONE)
      .render(&mut f, chunks[2]);

    let chunks = Layout::default()
      .direction(Direction::Horizontal)
      .constraints([Constraint::Length(2), Constraint::Percentage(100)].as_ref())
      .split(chunks[2]);

    let row_style = Style::default().fg(Color::White);
    let hr_style = Style::default().fg(Color::DarkGray);
    Table::new(
      ["", ""].into_iter(),
      vec![
        Row::StyledData(["--------", "---------------------------"].into_iter(), hr_style),
        Row::StyledData(["  alt-i ", " Switch to [Ideas] tab"].into_iter(), row_style),
        Row::StyledData(["  alt-j ", " Switch to [Journals] tab"].into_iter(), row_style),
        Row::StyledData(["  alt-n ", " Switch to [Notes] tab"].into_iter(), row_style),
        Row::StyledData(["  alt-t ", " Switch to [Tasks] tab"].into_iter(), row_style),
        Row::StyledData(["--------", "---------------------------"].into_iter(), hr_style),
        Row::StyledData(["  alt-h ", " Show help"].into_iter(), row_style),
        Row::StyledData(["--------", "---------------------------"].into_iter(), hr_style),
        Row::StyledData([" ctrl-w ", " Delete last word in input"].into_iter(), row_style),
        Row::StyledData(["    esc ", " Clear input"].into_iter(), row_style),
        Row::StyledData(["--------", "---------------------------"].into_iter(), hr_style),
        Row::StyledData(["  enter ", " Edit/create entry"].into_iter(), row_style),
        Row::StyledData(["  alt-s ", " Rotate task status"].into_iter(), row_style),
        Row::StyledData([" ctrl-d ", " Delete entry under cursor"].into_iter(), row_style),
        Row::StyledData(["--------", "---------------------------"].into_iter(), hr_style),
        Row::StyledData([" ctrl-q ", " Quit"].into_iter(), row_style),
      ]
      .into_iter(),
    )
    .widths(&[10, 30])
    .style(Style::default().fg(Color::White))
    .column_spacing(2)
    .render(&mut f, chunks[1]);
  })?;
  Ok(())
}