|
|
|
@ -6,7 +6,7 @@ use tui::{
|
|
|
|
|
backend::Backend,
|
|
|
|
|
layout::{Constraint, Rect},
|
|
|
|
|
style::{Color, Style},
|
|
|
|
|
widgets::{Block, Borders, Cell, Row, Table as WTable, TableState},
|
|
|
|
|
widgets::{Block, Borders, Cell, Row, Table, TableState},
|
|
|
|
|
Frame,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -15,7 +15,9 @@ pub struct TableComponent {
|
|
|
|
|
pub headers: Vec<String>,
|
|
|
|
|
pub rows: Vec<Vec<String>>,
|
|
|
|
|
pub column_index: usize,
|
|
|
|
|
pub column_page: usize,
|
|
|
|
|
pub scroll: VerticalScroll,
|
|
|
|
|
pub select_entire_row: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for TableComponent {
|
|
|
|
@ -24,13 +26,26 @@ impl Default for TableComponent {
|
|
|
|
|
state: TableState::default(),
|
|
|
|
|
headers: vec![],
|
|
|
|
|
rows: vec![],
|
|
|
|
|
column_page: 0,
|
|
|
|
|
column_index: 0,
|
|
|
|
|
scroll: VerticalScroll::new(),
|
|
|
|
|
select_entire_row: false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TableComponent {
|
|
|
|
|
pub fn reset(&mut self, headers: Vec<String>, rows: Vec<Vec<String>>) {
|
|
|
|
|
self.headers = headers;
|
|
|
|
|
self.rows = rows;
|
|
|
|
|
self.column_page = 0;
|
|
|
|
|
self.column_index = 1;
|
|
|
|
|
self.state.select(None);
|
|
|
|
|
if !self.rows.is_empty() {
|
|
|
|
|
self.state.select(Some(0));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn next(&mut self, lines: usize) {
|
|
|
|
|
let i = match self.state.selected() {
|
|
|
|
|
Some(i) => {
|
|
|
|
@ -42,17 +57,23 @@ impl TableComponent {
|
|
|
|
|
}
|
|
|
|
|
None => None,
|
|
|
|
|
};
|
|
|
|
|
self.select_entire_row = false;
|
|
|
|
|
self.state.select(i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn reset(&mut self, headers: Vec<String>, rows: Vec<Vec<String>>) {
|
|
|
|
|
self.headers = headers;
|
|
|
|
|
self.rows = rows;
|
|
|
|
|
self.column_index = 0;
|
|
|
|
|
self.state.select(None);
|
|
|
|
|
if !self.rows.is_empty() {
|
|
|
|
|
self.state.select(Some(0));
|
|
|
|
|
}
|
|
|
|
|
pub fn previous(&mut self, lines: usize) {
|
|
|
|
|
let i = match self.state.selected() {
|
|
|
|
|
Some(i) => {
|
|
|
|
|
if i <= lines {
|
|
|
|
|
Some(0)
|
|
|
|
|
} else {
|
|
|
|
|
Some(i - lines)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => None,
|
|
|
|
|
};
|
|
|
|
|
self.select_entire_row = false;
|
|
|
|
|
self.state.select(i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn scroll_top(&mut self) {
|
|
|
|
@ -70,34 +91,47 @@ impl TableComponent {
|
|
|
|
|
self.state.select(Some(self.rows.len() - 1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn previous(&mut self, lines: usize) {
|
|
|
|
|
let i = match self.state.selected() {
|
|
|
|
|
Some(i) => {
|
|
|
|
|
if i <= lines {
|
|
|
|
|
Some(0)
|
|
|
|
|
} else {
|
|
|
|
|
Some(i - lines)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => None,
|
|
|
|
|
};
|
|
|
|
|
self.state.select(i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn next_column(&mut self) {
|
|
|
|
|
if self.headers.len() > 9 && self.column_index < self.headers.len() - 9 {
|
|
|
|
|
self.column_index += 1
|
|
|
|
|
if self.rows.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if self.column_index == self.headers.len() - 1 {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if self.column_index == 9 {
|
|
|
|
|
self.next_column_page();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
self.select_entire_row = false;
|
|
|
|
|
self.column_index += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn previous_column(&mut self) {
|
|
|
|
|
if self.column_index > 0 {
|
|
|
|
|
self.column_index -= 1
|
|
|
|
|
if self.rows.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if self.column_index == 1 {
|
|
|
|
|
self.previous_column_page();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
self.select_entire_row = false;
|
|
|
|
|
self.column_index -= 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn next_column_page(&mut self) {
|
|
|
|
|
if self.headers.len() > 9 && self.column_page < self.headers.len() - 9 {
|
|
|
|
|
self.column_page += 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn previous_column_page(&mut self) {
|
|
|
|
|
if self.column_page > 0 {
|
|
|
|
|
self.column_page -= 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn headers(&self) -> Vec<String> {
|
|
|
|
|
let mut headers = self.headers[self.column_index..].to_vec();
|
|
|
|
|
let mut headers = self.headers[self.column_page..].to_vec();
|
|
|
|
|
headers.insert(0, "".to_string());
|
|
|
|
|
headers
|
|
|
|
|
}
|
|
|
|
@ -106,7 +140,7 @@ impl TableComponent {
|
|
|
|
|
let rows = self
|
|
|
|
|
.rows
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|row| row[self.column_index..].to_vec())
|
|
|
|
|
.map(|row| row[self.column_page..].to_vec())
|
|
|
|
|
.collect::<Vec<Vec<String>>>();
|
|
|
|
|
let mut new_rows = match self.state.selected() {
|
|
|
|
|
Some(index) => {
|
|
|
|
@ -146,25 +180,42 @@ impl DrawableComponent for TableComponent {
|
|
|
|
|
.map(|h| Cell::from(h.to_string()).style(Style::default()));
|
|
|
|
|
let header = Row::new(header_cells).height(1).bottom_margin(1);
|
|
|
|
|
let rows = self.rows();
|
|
|
|
|
let rows = rows.iter().map(|item| {
|
|
|
|
|
let rows = rows.iter().enumerate().map(|(row_index, item)| {
|
|
|
|
|
let height = item
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|content| content.chars().filter(|c| *c == '\n').count())
|
|
|
|
|
.max()
|
|
|
|
|
.unwrap_or(0)
|
|
|
|
|
+ 1;
|
|
|
|
|
let cells = item
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|c| Cell::from(c.to_string()).style(Style::default()));
|
|
|
|
|
let cells = item.iter().enumerate().map(|(column_page, c)| {
|
|
|
|
|
Cell::from(c.to_string()).style(if column_page == self.column_index {
|
|
|
|
|
match self.state.selected() {
|
|
|
|
|
Some(selected_row) => {
|
|
|
|
|
if row_index == selected_row {
|
|
|
|
|
Style::default().bg(Color::Blue)
|
|
|
|
|
} else {
|
|
|
|
|
Style::default()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => Style::default(),
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Style::default()
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
Row::new(cells).height(height as u16).bottom_margin(1)
|
|
|
|
|
});
|
|
|
|
|
let widths = (0..10)
|
|
|
|
|
.map(|_| Constraint::Percentage(10))
|
|
|
|
|
.collect::<Vec<Constraint>>();
|
|
|
|
|
let t = WTable::new(rows)
|
|
|
|
|
let t = Table::new(rows)
|
|
|
|
|
.header(header)
|
|
|
|
|
.block(Block::default().borders(Borders::ALL).title("Records"))
|
|
|
|
|
.highlight_style(Style::default().bg(Color::Blue))
|
|
|
|
|
.highlight_style(if self.select_entire_row {
|
|
|
|
|
Style::default().bg(Color::Blue)
|
|
|
|
|
} else {
|
|
|
|
|
Style::default()
|
|
|
|
|
})
|
|
|
|
|
.style(if focused {
|
|
|
|
|
Style::default()
|
|
|
|
|
} else {
|
|
|
|
@ -187,6 +238,7 @@ impl Component for TableComponent {
|
|
|
|
|
Key::Char('k') => self.previous(1),
|
|
|
|
|
Key::Ctrl('u') => self.previous(10),
|
|
|
|
|
Key::Char('g') => self.scroll_top(),
|
|
|
|
|
Key::Char('r') => self.select_entire_row = true,
|
|
|
|
|
Key::Shift('G') | Key::Shift('g') => self.scroll_bottom(),
|
|
|
|
|
Key::Char('l') => self.next_column(),
|
|
|
|
|
_ => (),
|
|
|
|
@ -194,3 +246,28 @@ impl Component for TableComponent {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod test {
|
|
|
|
|
use super::TableComponent;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_headers() {
|
|
|
|
|
let mut component = TableComponent::default();
|
|
|
|
|
component.headers = vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect();
|
|
|
|
|
assert_eq!(component.headers(), vec!["", "a", "b", "c"])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_rows() {
|
|
|
|
|
let mut component = TableComponent::default();
|
|
|
|
|
component.rows = vec![
|
|
|
|
|
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
|
|
|
|
|
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
|
|
|
|
|
];
|
|
|
|
|
assert_eq!(
|
|
|
|
|
component.rows(),
|
|
|
|
|
vec![vec!["1", "a", "b", "c"], vec!["2", "d", "e", "f"]],
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|