Files
managarr/src/ui/widgets/managarr_table.rs

197 lines
5.2 KiB
Rust

use crate::models::stateful_table::StatefulTable;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::layout_block_top_border;
use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::widgets::popup::Popup;
use crate::ui::widgets::selectable_list::SelectableList;
use crate::ui::HIGHLIGHT_SYMBOL;
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::prelude::{Style, Stylize, Text};
use ratatui::widgets::{Block, ListItem, Paragraph, Row, StatefulWidget, Table, Widget};
use std::fmt::Debug;
pub struct ManagarrTable<'a, T, F>
where
F: Fn(&T) -> Row<'a>,
T: Clone + PartialEq + Eq + Debug,
{
content: Option<&'a mut StatefulTable<T>>,
table_headers: Vec<String>,
constraints: Vec<Constraint>,
row_mapper: F,
footer: Option<String>,
footer_alignment: Alignment,
block: Block<'a>,
margin: u16,
is_loading: bool,
highlight_rows: bool,
is_sorting: bool,
}
impl<'a, T, F> ManagarrTable<'a, T, F>
where
F: Fn(&T) -> Row<'a>,
T: Clone + PartialEq + Eq + Debug,
{
pub fn new(content: Option<&'a mut StatefulTable<T>>, row_mapper: F) -> Self {
Self {
content,
table_headers: Vec::new(),
constraints: Vec::new(),
row_mapper,
footer: None,
footer_alignment: Alignment::Left,
block: Block::new(),
margin: 0,
is_loading: false,
highlight_rows: true,
is_sorting: false,
}
}
pub fn headers<I>(mut self, headers: I) -> Self
where
I: IntoIterator,
I::Item: Into<String>,
{
self.table_headers = headers.into_iter().map(Into::into).collect();
self
}
pub fn constraints<I>(mut self, constraints: I) -> Self
where
I: IntoIterator,
I::Item: Into<Constraint>,
{
self.constraints = constraints.into_iter().map(Into::into).collect();
self
}
pub fn footer(mut self, footer: Option<String>) -> Self {
self.footer = footer;
self
}
pub fn footer_alignment(mut self, alignment: Alignment) -> Self {
self.footer_alignment = alignment;
self
}
pub fn block(mut self, block: Block<'a>) -> Self {
self.block = block;
self
}
pub fn margin(mut self, margin: u16) -> Self {
self.margin = margin;
self
}
pub fn loading(mut self, is_loading: bool) -> Self {
self.is_loading = is_loading;
self
}
pub fn highlight_rows(mut self, highlight_rows: bool) -> Self {
self.highlight_rows = highlight_rows;
self
}
pub fn sorting(mut self, is_sorting: bool) -> Self {
self.is_sorting = is_sorting;
self
}
fn render_table(self, area: Rect, buf: &mut Buffer) {
let table_headers = self.parse_headers();
let table_area = if let Some(ref footer) = self.footer {
let [content_area, footer_area] =
Layout::vertical([Constraint::Fill(0), Constraint::Length(2)])
.margin(self.margin)
.areas(area);
Paragraph::new(Text::from(format!(" {footer}").help()))
.block(layout_block_top_border())
.alignment(self.footer_alignment)
.render(footer_area, buf);
content_area
} else {
area
};
let loading_block = LoadingBlock::new(self.is_loading, self.block.clone());
if let Some(content) = self.content {
let (table_contents, table_state) = if content.filtered_items.is_some() {
(
content.filtered_items.as_ref().unwrap(),
content.filtered_state.as_mut().unwrap(),
)
} else {
(&content.items, &mut content.state)
};
if !table_contents.is_empty() {
let rows = table_contents.iter().map(&self.row_mapper);
let headers = Row::new(table_headers).default().bold().bottom_margin(0);
let mut table = Table::new(rows, &self.constraints)
.header(headers)
.block(self.block);
if self.highlight_rows {
table = table
.highlight_style(Style::new().highlight())
.highlight_symbol(HIGHLIGHT_SYMBOL);
}
StatefulWidget::render(table, table_area, buf, table_state);
if content.sort.is_some() && self.is_sorting {
let selectable_list = SelectableList::new(content.sort.as_mut().unwrap(), |item| {
ListItem::new(Text::from(item.name))
});
Popup::new(selectable_list, 20, 50).render(table_area, buf);
}
} else {
loading_block.render(table_area, buf);
}
} else {
loading_block.render(table_area, buf);
}
}
fn parse_headers(&self) -> Vec<Text<'a>> {
if let Some(ref content) = self.content {
if let Some(ref sort_list) = content.sort {
if !self.is_sorting {
let mut new_headers = self.table_headers.clone();
let idx = sort_list.state.selected().unwrap_or(0);
let direction = if content.sort_asc { "" } else { "" };
new_headers[idx].push_str(direction);
return new_headers.into_iter().map(Text::from).collect();
}
}
}
self
.table_headers
.clone()
.into_iter()
.map(Text::from)
.collect()
}
}
impl<'a, T, F> Widget for ManagarrTable<'a, T, F>
where
F: Fn(&T) -> Row<'a>,
T: Clone + PartialEq + Eq + Debug,
{
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_table(area, buf);
}
}