use metatheca::{Uuid, format_iso8601_secs};
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Paragraph, Wrap};
use crate::snapshot::JotRow;
use crate::state::{AppState, Focus};
use crate::ui::border_style;
pub fn draw(f: &mut Frame, area: Rect, state: &mut AppState) {
let block = Block::bordered().border_style(border_style(state.focus == Focus::View));
let inner = block.inner(area);
f.render_widget(block, area);
if inner.height < 3 {
state.view_height = 0;
return;
}
let [meta, content] =
Layout::vertical([Constraint::Length(2), Constraint::Min(0)]).areas(inner);
let cblock = Block::new()
.borders(Borders::TOP)
.border_style(Style::new().add_modifier(Modifier::DIM));
let cinner = cblock.inner(content);
state.view_height = cinner.height;
f.render_widget(cblock, content);
let Some(row) = state.selected_row() else {
f.render_widget(
Paragraph::new("no jot selected").style(Style::new().add_modifier(Modifier::DIM)),
meta,
);
return;
};
f.render_widget(
Paragraph::new(vec![meta_line_1(row), meta_line_2(row, state)]),
meta,
);
let lines = state
.selected_row()
.and_then(|r| state.content_of(r.entry))
.map(|c| c.lines.clone())
.unwrap_or_default();
f.render_widget(
Paragraph::new(lines)
.wrap(Wrap { trim: false })
.scroll((state.view_scroll, 0)),
cinner,
);
}
fn meta_line_1(row: &JotRow) -> Line<'static> {
let mut spans = vec![
Span::styled(
format!("--{}--{}", row.profile, row.id),
Style::new().add_modifier(Modifier::BOLD),
),
Span::styled(
format!(" ({})", row.ext),
Style::new().add_modifier(Modifier::DIM),
),
];
if let Some(ns) = row.created_at_ns {
spans.push(Span::raw(" created "));
spans.push(Span::styled(
format_iso8601_secs(ns),
Style::new().add_modifier(Modifier::DIM),
));
}
if !row.attachments.is_empty() {
spans.push(Span::raw(format!(" {} att", row.attachments.len())));
}
let todos = &row.effective.todos;
if !todos.is_empty() {
let done = todos.iter().filter(|t| t.done).count();
spans.push(Span::raw(format!(" {done}/{} todos", todos.len())));
}
Line::from(spans)
}
fn meta_line_2(row: &JotRow, state: &AppState) -> Line<'static> {
let e = &row.effective;
let mut spans = Vec::new();
for t in &e.tags {
spans.push(Span::styled(
format!("..{t}"),
Style::new().fg(Color::Yellow),
));
spans.push(Span::raw(" "));
}
for c in &e.categories {
spans.push(Span::styled(
format!("::{c}"),
Style::new().fg(Color::Green),
));
spans.push(Span::raw(" "));
}
if !e.edges.is_empty() {
spans.push(Span::styled(
" refs: ",
Style::new().add_modifier(Modifier::DIM),
));
for (entry_str, infos) in &e.edges {
for info in infos {
let mut label = edge_ref(state, entry_str, &info.written_as);
if let Some(cat) = &info.annot.category {
label.push_str("::");
label.push_str(cat);
}
spans.push(Span::styled(label, Style::new().fg(Color::Cyan)));
spans.push(Span::raw(" "));
}
}
}
if !row.attachments.is_empty() {
spans.push(Span::styled(
" att: ",
Style::new().add_modifier(Modifier::DIM),
));
for a in &row.attachments {
spans.push(Span::styled(a.clone(), Style::new().fg(Color::Magenta)));
spans.push(Span::raw(" "));
}
}
if spans.is_empty() {
spans.push(Span::styled(
"(no metadata)",
Style::new().add_modifier(Modifier::DIM),
));
}
Line::from(spans)
}
fn edge_ref(state: &AppState, entry_str: &str, written: &str) -> String {
Uuid::parse_str(entry_str)
.ok()
.and_then(|u| state.snapshot.by_entry.get(&u))
.map(|&i| state.snapshot.rows[i].short_ref(state.scope()))
.unwrap_or_else(|| format!("{written}?"))
}