Skip to main content

taba_cli/tui/screens/profiles/
view.rs

1//! The drawing of the Profiles tab: the profiles of the file, which one is in use and which is the default.
2
3use ratatui::layout::{Constraint, Layout, Rect};
4use ratatui::style::Style;
5use ratatui::text::{Line, Span};
6use ratatui::widgets::{Block, Cell, Paragraph, Row as TableRow, Table, Wrap};
7use ratatui::Frame;
8
9use crate::profile::{Profile, DEFAULT_URL};
10use crate::tui::model::Model;
11use crate::tui::screens::profiles::selected_index;
12
13/// How a profile holds its token, for the table: never the token itself.
14pub fn token_kind(m: &Model, profile: &Profile) -> String {
15    let tx = m.tx();
16    if profile.get("token-file").is_some() {
17        tx.t("profiles.token-file")
18    } else if profile.get("token").is_some() {
19        tx.t("profiles.token-inline")
20    } else {
21        tx.t("profiles.token-none")
22    }
23}
24
25pub fn draw(f: &mut Frame, area: Rect, m: &Model) {
26    let tx = m.tx();
27    let th = m.theme;
28    let file = &m.profiles.file;
29    if file.profiles.is_empty() {
30        f.render_widget(
31            Paragraph::new(vec![
32                Line::from(Span::styled(tx.t("profiles.none"), th.warning())),
33                Line::default(),
34                Line::from(tx.tw("profiles.builtin", &[("url", &DEFAULT_URL)])),
35                Line::from(tx.t("profiles.create-first")),
36            ])
37            .block(Block::bordered())
38            .wrap(Wrap { trim: false }),
39            area,
40        );
41        return;
42    }
43    let [table, detail] =
44        Layout::vertical([Constraint::Min(5), Constraint::Length(10)]).areas(area);
45    let selected = selected_index(m);
46    let capacity = usize::from(table.height.saturating_sub(3)).max(1);
47    let start = selected.map_or(0, |s| s.saturating_sub(capacity - 1));
48    let rows: Vec<TableRow> = file
49        .profiles
50        .iter()
51        .enumerate()
52        .skip(start)
53        .take(capacity)
54        .map(|(i, (name, profile))| {
55            let marker = if selected == Some(i) {
56                th.selector()
57            } else {
58                "  "
59            };
60            let default = if file.default.as_deref() == Some(name.as_str()) {
61                "●"
62            } else {
63                "○"
64            };
65            let in_use = if *name == m.profile {
66                tx.t("profiles.in-use")
67            } else {
68                String::new()
69            };
70            let row = TableRow::new([
71                Cell::from(format!("{marker}{name}")),
72                Cell::from(profile.get("url").unwrap_or(DEFAULT_URL).to_owned()),
73                Cell::from(token_kind(m, profile)),
74                Cell::from(default),
75                Cell::from(in_use).style(th.ok()),
76            ]);
77            if selected == Some(i) {
78                row.style(th.selected())
79            } else {
80                row
81            }
82        })
83        .collect();
84    let widths = [
85        Constraint::Length(16),
86        Constraint::Fill(1),
87        Constraint::Length(12),
88        Constraint::Length(8),
89        Constraint::Length(9),
90    ];
91    f.render_widget(
92        Table::new(rows, widths)
93            .header(
94                TableRow::new([
95                    tx.t("col.name"),
96                    tx.t("col.url"),
97                    tx.t("col.token"),
98                    tx.t("col.default"),
99                    String::new(),
100                ])
101                .style(th.title()),
102            )
103            .block(Block::bordered().border_style(th.accent())),
104        table,
105    );
106    draw_detail(f, detail, m);
107}
108
109fn draw_detail(f: &mut Frame, area: Rect, m: &Model) {
110    let tx = m.tx();
111    let th = m.theme;
112    let block = Block::bordered().title(format!(" {} ", tx.t("profiles.detail")));
113    let Some(i) = selected_index(m) else {
114        f.render_widget(block, area);
115        return;
116    };
117    let (name, profile) = &m.profiles.file.profiles[i];
118    let mut lines = vec![Line::from(Span::styled(name.clone(), th.title()))];
119    for (field, value) in profile.entries() {
120        // A token kept in the file is never drawn.
121        let shown = if field == "token" {
122            "••••••••".to_owned()
123        } else {
124            value.to_owned()
125        };
126        lines.push(Line::from(vec![
127            Span::styled(format!("{field:<16}"), th.dim()),
128            Span::styled(shown, Style::default()),
129        ]));
130    }
131    lines.push(Line::default());
132    lines.push(Line::from(Span::styled(
133        tx.t("profiles.session-note"),
134        th.dim(),
135    )));
136    f.render_widget(
137        Paragraph::new(lines)
138            .block(block)
139            .wrap(Wrap { trim: false }),
140        area,
141    );
142}