taba_cli/tui/screens/profiles/
view.rs1use 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
13pub 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 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}