use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use ratatui::widgets::{
Block, Clear, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, Wrap,
};
use ratatui::Frame;
use crate::tui::api::{Missing, Severity};
use crate::tui::model::{short_id, Fail, Model};
use crate::tui::theme::Theme;
pub fn centered(area: Rect, width: u16, height: u16) -> Rect {
let width = width.min(area.width);
let height = height.min(area.height);
let [_, row, _] = Layout::vertical([
Constraint::Fill(1),
Constraint::Length(height),
Constraint::Fill(1),
])
.areas(area);
let [_, cell, _] = Layout::horizontal([
Constraint::Fill(1),
Constraint::Length(width),
Constraint::Fill(1),
])
.areas(row);
cell
}
pub fn popup(f: &mut Frame, area: Rect, title: &str, style: Style) -> Rect {
f.render_widget(Clear, area);
let block = Block::bordered()
.title(format!(" {title} "))
.border_style(style);
let inner = block.inner(area);
f.render_widget(block, area);
inner
}
pub fn scrolling(f: &mut Frame, area: Rect, lines: Vec<Line<'static>>, offset: u16) -> u16 {
let total = lines.len();
let height = usize::from(area.height);
let max = total.saturating_sub(height);
let used = usize::from(offset).min(max);
let overflow = total > height;
let text_area = if overflow && area.width > 2 {
Rect {
width: area.width - 1,
..area
}
} else {
area
};
f.render_widget(
Paragraph::new(lines)
.wrap(Wrap { trim: false })
.scroll((u16::try_from(used).unwrap_or(u16::MAX), 0)),
text_area,
);
if overflow {
let mut state = ScrollbarState::new(max).position(used);
f.render_stateful_widget(
Scrollbar::new(ScrollbarOrientation::VerticalRight),
area,
&mut state,
);
}
u16::try_from(used).unwrap_or(u16::MAX)
}
pub fn partial_seal(m: &Model, theme: Theme, missing: &[Missing]) -> Line<'static> {
let tx = m.tx();
let names: Vec<String> = missing
.iter()
.map(|n| {
let short = short_id(&n.node_id);
if n.reason.is_empty() {
short
} else {
format!("{short} ({})", n.reason)
}
})
.collect();
let text = if names.is_empty() {
tx.t("seal.partial")
} else {
tx.tw("seal.partial-nodes", &[("nodes", &names.join(", "))])
};
Line::from(Span::styled(
format!("{} {text}", theme.icon(Severity::Warning)),
theme.warning(),
))
}
pub fn denied_text(m: &Model, fail: &Fail) -> String {
let tx = m.tx();
match fail.required.as_deref() {
Some("root") => tx.t("denied.root"),
Some(permission) => tx.tw("denied.permission", &[("permission", &permission)]),
None if fail.status == 401 => tx.t("denied.unauthorized"),
None => tx.t("denied.generic"),
}
}
pub fn centered_line(text: impl Into<String>, style: Style) -> Paragraph<'static> {
Paragraph::new(Line::from(Span::styled(text.into(), style))).alignment(Alignment::Center)
}
pub fn short(id: &str) -> String {
short_id(id)
}
#[cfg(test)]
mod tests;