|
|
|
@ -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,
|
|
|
|
|