use table component

pull/12/head
Takayuki Maeda 3 years ago
parent 87ae252bf6
commit 4fadd8cc87

@ -1,18 +1,11 @@
use crate::components::utils::scroll_vertical::VerticalScroll;
use crate::{
components::DatabasesComponent,
components::{DatabasesComponent, TableComponent},
user_config::{Connection, UserConfig},
};
use sqlx::mysql::MySqlPool;
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use tui::{
backend::Backend,
layout::{Constraint, Rect},
style::{Color, Style},
widgets::{Block, Borders, Cell, ListState, Row, Table as WTable, TableState},
Frame,
};
use tui::widgets::ListState;
use unicode_width::UnicodeWidthStr;
#[derive(Debug, Clone, Copy, EnumIter)]
@ -37,7 +30,7 @@ impl Tab {
pub enum FocusBlock {
DabataseList,
RecordTable,
Table,
ConnectionList,
Query,
}
@ -53,165 +46,12 @@ pub struct Column {
#[sqlx(rename = "Null")]
pub null: String,
}
pub struct RecordTable {
pub state: TableState,
pub headers: Vec<String>,
pub rows: Vec<Vec<String>>,
pub column_index: usize,
pub scroll: VerticalScroll,
}
impl Default for RecordTable {
fn default() -> Self {
Self {
state: TableState::default(),
headers: vec![],
rows: vec![],
column_index: 0,
scroll: VerticalScroll::new(),
}
}
}
impl RecordTable {
pub fn next(&mut self, lines: usize) {
let i = match self.state.selected() {
Some(i) => {
if i >= self.rows.len() - lines {
Some(self.rows.len() - 1)
} else {
Some(i + lines)
}
}
None => None,
};
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);
self.state.select(Some(0));
}
pub fn scroll_top(&mut self) {
self.state.select(Some(0));
}
pub fn scroll_bottom(&mut self) {
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
}
}
pub fn previous_column(&mut self) {
if self.column_index > 0 {
self.column_index -= 1
}
}
pub fn headers(&self) -> Vec<String> {
let mut headers = self.headers[self.column_index..].to_vec();
headers.insert(0, "".to_string());
headers
}
pub fn rows(&self) -> Vec<Vec<String>> {
let mut rows = self
.rows
.iter()
.map(|row| row[self.column_index..].to_vec())
.collect::<Vec<Vec<String>>>();
for (index, row) in rows.iter_mut().enumerate() {
row.insert(0, (index + 1).to_string())
}
rows
}
pub fn draw<B: Backend>(
&mut self,
f: &mut Frame<'_, B>,
layout_chunk: Rect,
focused: bool,
) -> anyhow::Result<()> {
self.state.selected().map_or_else(
|| {
self.scroll.reset();
},
|selection| {
self.scroll.update(
selection,
self.rows.len(),
layout_chunk.height.saturating_sub(2) as usize,
);
},
);
let headers = self.headers();
let header_cells = headers
.iter()
.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 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()));
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)
.header(header)
.block(Block::default().borders(Borders::ALL).title("Records"))
.highlight_style(Style::default().bg(Color::Blue))
.style(if focused {
Style::default()
} else {
Style::default().fg(Color::DarkGray)
})
.widths(&widths);
f.render_stateful_widget(t, layout_chunk, &mut self.state);
self.scroll.draw(f, layout_chunk);
Ok(())
}
}
pub struct App {
pub input: String,
pub input_cursor_x: u16,
pub query: String,
pub record_table: RecordTable,
pub structure_table: RecordTable,
pub record_table: TableComponent,
pub structure_table: TableComponent,
pub focus_block: FocusBlock,
pub selected_tab: Tab,
pub user_config: Option<UserConfig>,
@ -229,8 +69,8 @@ impl Default for App {
input: String::new(),
input_cursor_x: 0,
query: String::new(),
record_table: RecordTable::default(),
structure_table: RecordTable::default(),
record_table: TableComponent::default(),
structure_table: TableComponent::default(),
focus_block: FocusBlock::DabataseList,
selected_tab: Tab::Records,
user_config: None,

@ -2,6 +2,7 @@ pub mod connection_list;
pub mod database_list;
pub mod query;
pub mod record_table;
pub mod structure_table;
use crate::app::{App, FocusBlock, Tab};
use crate::event::Key;
@ -10,7 +11,10 @@ pub async fn handle_app(key: Key, app: &mut App) -> anyhow::Result<()> {
match app.focus_block {
FocusBlock::ConnectionList => connection_list::handler(key, app).await?,
FocusBlock::DabataseList => database_list::handler(key, app).await?,
FocusBlock::RecordTable => record_table::handler(key, app).await?,
FocusBlock::Table => match app.selected_tab {
Tab::Records => record_table::handler(key, app).await?,
Tab::Structure => structure_table::handler(key, app).await?,
},
FocusBlock::Query => query::handler(key, app).await?,
}
match key {
@ -20,7 +24,7 @@ pub async fn handle_app(key: Key, app: &mut App) -> anyhow::Result<()> {
},
Key::Char('r') => match app.focus_block {
FocusBlock::Query => (),
_ => app.focus_block = FocusBlock::RecordTable,
_ => app.focus_block = FocusBlock::Table,
},
Key::Char('e') => app.focus_block = FocusBlock::Query,
Key::Char('1') => app.selected_tab = Tab::Records,
@ -28,6 +32,5 @@ pub async fn handle_app(key: Key, app: &mut App) -> anyhow::Result<()> {
Key::Esc => app.error = None,
_ => (),
}
app.databases.focused = matches!(app.focus_block, FocusBlock::DabataseList);
Ok(())
}

@ -1,19 +1,12 @@
use crate::app::{App, FocusBlock};
use crate::components::Component as _;
use crate::event::Key;
pub async fn handler(key: Key, app: &mut App) -> anyhow::Result<()> {
match key {
Key::Char('h') => app.record_table.previous_column(),
Key::Char('j') => app.record_table.next(1),
Key::Ctrl('d') => app.record_table.next(10),
Key::Char('k') => app.record_table.previous(1),
Key::Ctrl('u') => app.record_table.previous(10),
Key::Char('g') => app.record_table.scroll_top(),
Key::Shift('G') | Key::Shift('g') => app.record_table.scroll_bottom(),
Key::Char('l') => app.record_table.next_column(),
Key::Left => app.focus_block = FocusBlock::DabataseList,
Key::Char('c') => app.focus_block = FocusBlock::ConnectionList,
_ => (),
key => app.record_table.event(key)?,
}
Ok(())
}

@ -0,0 +1,12 @@
use crate::app::{App, FocusBlock};
use crate::components::Component as _;
use crate::event::Key;
pub async fn handler(key: Key, app: &mut App) -> anyhow::Result<()> {
match key {
Key::Left => app.focus_block = FocusBlock::DabataseList,
Key::Char('c') => app.focus_block = FocusBlock::ConnectionList,
key => app.structure_table.event(key)?,
}
Ok(())
}

@ -71,7 +71,13 @@ pub fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<(
.constraints([Constraint::Min(8), Constraint::Length(7)].as_ref())
.split(main_chunks[0]);
app.databases.draw(f, left_chunks[0]).unwrap();
app.databases
.draw(
f,
left_chunks[0],
matches!(app.focus_block, FocusBlock::DabataseList),
)
.unwrap();
let table_status: Vec<ListItem> = app
.table_status()
@ -126,9 +132,13 @@ pub fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<(
Tab::Records => app.record_table.draw(
f,
right_chunks[2],
matches!(app.focus_block, FocusBlock::RecordTable),
matches!(app.focus_block, FocusBlock::Table),
)?,
Tab::Structure => app.structure_table.draw(
f,
right_chunks[2],
matches!(app.focus_block, FocusBlock::Table),
)?,
Tab::Structure => draw_structure_table(f, app, right_chunks[2])?,
}
if let Some(err) = app.error.clone() {
draw_error_popup(f, err)?;
@ -136,45 +146,6 @@ pub fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<(
Ok(())
}
fn draw_structure_table<B: Backend>(
f: &mut Frame<'_, B>,
app: &mut App,
layout_chunk: Rect,
) -> anyhow::Result<()> {
let headers = app.structure_table.headers();
let header_cells = headers
.iter()
.map(|h| Cell::from(h.to_string()).style(Style::default()));
let header = Row::new(header_cells).height(1).bottom_margin(1);
let rows = app.structure_table.rows();
let rows = rows.iter().map(|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()));
Row::new(cells).height(height as u16).bottom_margin(1)
});
let widths = (0..10)
.map(|_| Constraint::Percentage(10))
.collect::<Vec<Constraint>>();
let t = Table::new(rows)
.header(header)
.block(Block::default().borders(Borders::ALL).title("Structure"))
.highlight_style(Style::default().bg(Color::Blue))
.style(match app.focus_block {
FocusBlock::RecordTable => Style::default(),
_ => Style::default().fg(Color::DarkGray),
})
.widths(&widths);
f.render_stateful_widget(t, layout_chunk, &mut app.structure_table.state);
Ok(())
}
fn draw_error_popup<B: Backend>(f: &mut Frame<'_, B>, error: String) -> anyhow::Result<()> {
let percent_x = 60;
let percent_y = 20;

Loading…
Cancel
Save