Skip to main content

max / makenotwork

4.7 KB · 152 lines History Blame Raw
1 //! Collections management screen — list collections.
2
3 use makeover_tui::makeover_layout::{Column, Priority, Width};
4 use makeover_tui::table::{self, Cell, Sizing, TableStyle};
5 use ratatui::Frame;
6 use ratatui::layout::{Constraint, Layout};
7 use ratatui::style::{Modifier, Style};
8 use ratatui::text::{Line, Span};
9 use ratatui::widgets::{Block, Borders, Paragraph, TableState};
10
11 use super::App;
12
13 /// The collection columns. The title names the collection; whether it is public
14 /// is the next thing asked of it. The slug and the count drop first.
15 const COLLECTION_COLUMNS: [Column<'static>; 4] = [
16 Column {
17 name: "Title",
18 width: Width::Fill,
19 priority: Priority::Essential,
20 sortable: false,
21 sorted: None,
22 },
23 Column {
24 name: "Slug",
25 width: Width::Fixed,
26 priority: Priority::Optional,
27 sortable: false,
28 sorted: None,
29 },
30 Column {
31 name: "Status",
32 width: Width::Fixed,
33 priority: Priority::Secondary,
34 sortable: false,
35 sorted: None,
36 },
37 Column {
38 name: "Items",
39 width: Width::Fixed,
40 priority: Priority::Optional,
41 sortable: false,
42 sorted: None,
43 },
44 ];
45
46 /// The tracks the hand-written `Constraint`s carried.
47 const COLLECTION_SIZING: Sizing<'static> = Sizing {
48 lengths: &[("Title", 20), ("Slug", 20), ("Status", 8), ("Items", 6)],
49 fallback: 8,
50 };
51
52 pub(crate) fn render(frame: &mut Frame, app: &App) {
53 let area = frame.area();
54
55 let title = Line::from(vec![
56 Span::styled(
57 " Makenot.work ",
58 Style::default().add_modifier(Modifier::BOLD),
59 ),
60 Span::raw(" -- "),
61 Span::styled("Collections", Style::default().add_modifier(Modifier::BOLD)),
62 Span::raw(" "),
63 ]);
64
65 let block = Block::default()
66 .title(title)
67 .borders(Borders::ALL)
68 .border_style(Style::default().fg(app.theme.line_border));
69
70 let inner = block.inner(area);
71 frame.render_widget(block, area);
72
73 let chunks = Layout::vertical([
74 Constraint::Length(1), // spacer
75 Constraint::Length(1), // section header
76 Constraint::Min(3), // list
77 Constraint::Length(1), // status line
78 Constraint::Length(1), // keybindings
79 ])
80 .split(inner);
81
82 let count = app.collections.len();
83 let header = Paragraph::new(Line::from(vec![
84 Span::raw(" "),
85 Span::styled("Collections", Style::default().add_modifier(Modifier::BOLD)),
86 if count == 0 {
87 Span::raw("")
88 } else {
89 Span::raw(format!(" ({count})"))
90 },
91 ]));
92 frame.render_widget(header, chunks[1]);
93
94 if app.loading {
95 let loading = Paragraph::new(" Loading...");
96 frame.render_widget(loading, chunks[2]);
97 } else if app.collections.is_empty() {
98 let empty =
99 Paragraph::new(" No collections. Manage collections at makenot.work/dashboard");
100 frame.render_widget(empty, chunks[2]);
101 } else {
102 let area = super::indent(chunks[2], 2);
103 let rows: Vec<Vec<Cell>> = app
104 .collections
105 .iter()
106 .map(|c| {
107 vec![
108 Cell::new("Title", c.title.as_str()),
109 Cell::new("Slug", c.slug.as_str()),
110 Cell::new("Status", if c.is_public { "public" } else { "draft" }),
111 Cell::new("Items", c.item_count.to_string()),
112 ]
113 })
114 .collect();
115
116 let style = TableStyle::from_theme(&app.theme);
117 let table = table::table(
118 &COLLECTION_COLUMNS,
119 &rows,
120 &COLLECTION_SIZING,
121 &style,
122 area.width,
123 );
124 let mut state = TableState::default().with_selected(Some(app.selected_index));
125 frame.render_stateful_widget(table, area, &mut state);
126 }
127
128 if let Some(ref status) = app.collections_status {
129 let style = if status.starts_with("Error") {
130 Style::default().fg(app.theme.status_danger)
131 } else {
132 Style::default().fg(app.theme.status_success)
133 };
134 let status_line = Paragraph::new(format!(" {status}")).style(style);
135 frame.render_widget(status_line, chunks[3]);
136 }
137
138 let key_spans = vec![
139 Span::raw(" "),
140 Span::styled("[j/k]", Style::default().add_modifier(Modifier::BOLD)),
141 Span::raw(" Nav "),
142 Span::styled("[r]", Style::default().add_modifier(Modifier::BOLD)),
143 Span::raw(" Refresh "),
144 Span::styled("[Esc]", Style::default().add_modifier(Modifier::BOLD)),
145 Span::raw(" Back"),
146 ];
147
148 let keys =
149 Paragraph::new(Line::from(key_spans)).style(Style::default().fg(app.theme.content_muted));
150 frame.render_widget(keys, chunks[4]);
151 }
152