tabiew 0.13.1

A lightweight TUI application to view and query tabular data files, such as CSV, TSV, and parquet.
use std::ops::Div;

use crossterm::event::{KeyCode, KeyModifiers};
use ratatui::{
    layout::{Constraint, Flex, Layout},
    widgets::{Clear, Paragraph, Widget, Wrap},
};

use crate::{
    handler::message::Message,
    misc::{config::theme, sql::sql},
    tui::{
        component::Component,
        schema::{data_frame_info::DataFrameInfo, data_frame_names::DataFrameNames},
        widgets::block::Block,
    },
};

#[derive(Debug, Default)]
pub struct Schema {
    names: DataFrameNames,
    info: Option<DataFrameInfo>,
}

impl Component for Schema {
    fn render(
        &mut self,
        area: ratatui::prelude::Rect,
        buf: &mut ratatui::prelude::Buffer,
        focus_state: crate::tui::component::FocusState,
    ) {
        //
        // |----------------------------|
        // |     |          2           |
        // |     |----------------------|
        // |  1  |                      |
        // |     |          3           |
        // |     |                      |
        // |     |                      |
        // |----------------------------|
        //
        // 1: Table names
        // 2,3: Info
        //   2: Meta info
        //   3: Fields info

        buf.set_style(area, theme().text());
        Widget::render(Clear, area, buf);

        if let Some(selected) = self.names.selected()
            && let Some((_, new_info)) = sql().schema().get_by_index(selected)
            && self
                .info
                .as_ref()
                .map(|df_info| df_info.table_info())
                .map(|cur_info| cur_info != new_info)
                .unwrap_or(true)
        {
            self.info = Some(DataFrameInfo::new(new_info.clone()))
        }

        if sql().schema().is_empty() {
            let pg = Paragraph::new(
                        "No data frame found in the backed. Use the 'import' command to import data frames from files.",
                    ).centered().wrap(Wrap { trim: true });
            let width = area.width.saturating_sub(2).div(3).min(64);
            let lines = pg.line_count(width) as u16;
            let [center] = Layout::vertical([Constraint::Length(lines)])
                .flex(Flex::Center)
                .areas(area);
            let [center] = Layout::horizontal([Constraint::Length(width)])
                .flex(Flex::Center)
                .areas(center);
            Block::default().render(area, buf);
            pg.render(center, buf);
        } else {
            let [area1, area23] =
                Layout::horizontal([Constraint::Length(40), Constraint::Fill(1)]).areas(area);

            self.names.render(area1, buf, focus_state);
            if let Some(info) = self.info.as_mut() {
                info.render(area23, buf, focus_state);
            }
        }
    }
    fn handle(&mut self, event: crossterm::event::KeyEvent) -> bool {
        self.names.handle(event)
            || self
                .info
                .as_mut()
                .map(|info| info.handle(event))
                .unwrap_or_default()
            || match (event.code, event.modifiers) {
                (KeyCode::Esc, KeyModifiers::NONE) | (KeyCode::Char('q'), KeyModifiers::NONE) => {
                    Message::AppDismissSchema.enqueue();
                    true
                }
                (KeyCode::Enter, KeyModifiers::NONE) => {
                    if let Some((name, df)) = self
                        .names
                        .selected()
                        .and_then(|idx| {
                            sql()
                                .schema()
                                .get_by_index(idx)
                                .map(|(name, _)| name.to_owned())
                        })
                        .and_then(|name| {
                            sql()
                                .execute(
                                    &format!("SELECT * FROM \"{}\"", name.replace('"', "\"\"")),
                                    None,
                                )
                                .ok()
                                .map(|df| (name, df))
                        })
                    {
                        Message::TabsAddNamePane(df, name).enqueue();
                        Message::AppDismissSchema.enqueue();
                    }
                    true
                }
                _ => false,
            }
    }
}