499 lines
19 KiB
Rust
499 lines
19 KiB
Rust
// src/components/admin/add_table.rs
|
|
use crate::config::colors::themes::Theme;
|
|
use crate::state::app::highlight::HighlightState;
|
|
use crate::state::app::state::AppState;
|
|
use crate::state::pages::add_table::{AddTableFocus, AddTableState};
|
|
use crate::state::pages::canvas_state::CanvasState;
|
|
// use crate::state::pages::add_table::{ColumnDefinition, LinkDefinition}; // Not directly used here
|
|
use ratatui::{
|
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
|
style::{Modifier, Style},
|
|
text::{Line, Span},
|
|
widgets::{Block, BorderType, Borders, Cell, Paragraph, Row, Table},
|
|
Frame,
|
|
};
|
|
use crate::components::handlers::canvas::render_canvas;
|
|
use crate::components::common::dialog;
|
|
|
|
/// Renders the Add New Table page layout, structuring the display of table information,
|
|
/// input fields, and action buttons. Adapts layout based on terminal width.
|
|
pub fn render_add_table(
|
|
f: &mut Frame,
|
|
area: Rect,
|
|
theme: &Theme,
|
|
app_state: &AppState, // Currently unused, might be needed later
|
|
add_table_state: &mut AddTableState,
|
|
is_edit_mode: bool, // Determines if canvas inputs are in edit mode
|
|
highlight_state: &HighlightState, // For text highlighting in canvas
|
|
) {
|
|
// --- Configuration ---
|
|
// Threshold width to switch between wide and narrow layouts
|
|
const NARROW_LAYOUT_THRESHOLD: u16 = 120; // Adjust this value as needed
|
|
|
|
// --- State Checks ---
|
|
let focus_on_canvas_inputs = matches!(
|
|
add_table_state.current_focus,
|
|
AddTableFocus::InputTableName
|
|
| AddTableFocus::InputColumnName
|
|
| AddTableFocus::InputColumnType
|
|
);
|
|
|
|
// --- Main Page Block ---
|
|
let main_block = Block::default()
|
|
.title(" Add New Table ")
|
|
.title_alignment(Alignment::Center)
|
|
.borders(Borders::ALL)
|
|
.border_type(BorderType::Rounded)
|
|
.border_style(Style::default().fg(theme.border))
|
|
.style(Style::default().bg(theme.bg));
|
|
let inner_area = main_block.inner(area);
|
|
f.render_widget(main_block, area);
|
|
|
|
// --- Fullscreen Columns Table Check (Narrow Screens Only) ---
|
|
if area.width < NARROW_LAYOUT_THRESHOLD && add_table_state.current_focus == AddTableFocus::InsideColumnsTable {
|
|
// Render ONLY the columns table taking the full inner area
|
|
let columns_border_style = Style::default().fg(theme.highlight); // Always highlighted when fullscreen
|
|
let column_rows: Vec<Row<'_>> = add_table_state
|
|
.columns
|
|
.iter()
|
|
.map(|col_def| {
|
|
Row::new(vec![
|
|
Cell::from(if col_def.selected { "[*]" } else { "[ ]" }),
|
|
Cell::from(col_def.name.clone()),
|
|
Cell::from(col_def.data_type.clone()),
|
|
])
|
|
.style(Style::default().fg(theme.fg))
|
|
})
|
|
.collect();
|
|
let header_cells = ["Sel", "Name", "Type"]
|
|
.iter()
|
|
.map(|h| Cell::from(*h).style(Style::default().fg(theme.accent)));
|
|
let header = Row::new(header_cells).height(1).bottom_margin(1);
|
|
let columns_table = Table::new(column_rows, [Constraint::Length(5), Constraint::Percentage(50), Constraint::Percentage(50)])
|
|
.header(header)
|
|
.block(
|
|
Block::default()
|
|
.title(Span::styled(" Columns (Fullscreen) ", theme.fg)) // Indicate fullscreen
|
|
.title_alignment(Alignment::Center)
|
|
.borders(Borders::ALL)
|
|
.border_type(BorderType::Rounded)
|
|
.border_style(columns_border_style),
|
|
)
|
|
.row_highlight_style(
|
|
Style::default()
|
|
.add_modifier(Modifier::REVERSED)
|
|
.fg(theme.highlight),
|
|
)
|
|
.highlight_symbol(" > "); // Use the inside symbol
|
|
f.render_stateful_widget(columns_table, inner_area, &mut add_table_state.column_table_state);
|
|
return; // IMPORTANT: Stop rendering here for fullscreen mode
|
|
}
|
|
|
|
// --- Area Variable Declarations ---
|
|
let top_info_area: Rect;
|
|
let columns_area: Rect;
|
|
let canvas_area: Rect;
|
|
let add_button_area: Rect;
|
|
let indexes_area: Rect;
|
|
let links_area: Rect;
|
|
let bottom_buttons_area: Rect;
|
|
|
|
// --- Layout Decision ---
|
|
if area.width >= NARROW_LAYOUT_THRESHOLD {
|
|
// --- WIDE Layout (Based on first screenshot) ---
|
|
let main_chunks = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints([
|
|
Constraint::Length(3), // Top Info (Profile/Table Name) - Increased to 3 lines
|
|
Constraint::Min(10), // Middle Area (Columns | Right Pane)
|
|
Constraint::Length(3), // Bottom Buttons
|
|
])
|
|
.split(inner_area);
|
|
|
|
top_info_area = main_chunks[0];
|
|
let middle_area = main_chunks[1];
|
|
bottom_buttons_area = main_chunks[2];
|
|
|
|
// Split Middle Horizontally
|
|
let middle_chunks = Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints([
|
|
Constraint::Percentage(50), // Left: Columns Table
|
|
Constraint::Percentage(50), // Right: Inputs etc.
|
|
])
|
|
.split(middle_area);
|
|
|
|
columns_area = middle_chunks[0];
|
|
let right_pane_area = middle_chunks[1];
|
|
|
|
// Split Right Pane Vertically
|
|
let right_pane_chunks = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints([
|
|
Constraint::Length(5), // Input Canvas Area
|
|
Constraint::Length(3), // Add Button Area
|
|
Constraint::Min(5), // Indexes & Links Area
|
|
])
|
|
.split(right_pane_area);
|
|
|
|
canvas_area = right_pane_chunks[0];
|
|
add_button_area = right_pane_chunks[1];
|
|
let indexes_links_area = right_pane_chunks[2];
|
|
|
|
// Split Indexes/Links Horizontally
|
|
let indexes_links_chunks = Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints([
|
|
Constraint::Percentage(50), // Indexes Table
|
|
Constraint::Percentage(50), // Links Table
|
|
])
|
|
.split(indexes_links_area);
|
|
indexes_area = indexes_links_chunks[0];
|
|
links_area = indexes_links_chunks[1];
|
|
|
|
// --- Top Info Rendering (Wide - 2 lines) ---
|
|
let profile_text = Paragraph::new(vec![
|
|
Line::from(Span::styled(
|
|
format!("Profile: {}", add_table_state.profile_name),
|
|
theme.fg,
|
|
)),
|
|
Line::from(Span::styled(
|
|
format!("Table name: {}", add_table_state.table_name),
|
|
theme.fg,
|
|
)),
|
|
])
|
|
.block(
|
|
Block::default()
|
|
.borders(Borders::BOTTOM)
|
|
.border_style(Style::default().fg(theme.secondary)),
|
|
);
|
|
f.render_widget(profile_text, top_info_area);
|
|
} else {
|
|
// --- NARROW Layout (Based on second screenshot) ---
|
|
let main_chunks = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints([
|
|
Constraint::Length(1), // Top: Profile & Table Name (Single Row)
|
|
Constraint::Length(5), // Column Definition Input Canvas Area
|
|
Constraint::Length(3), // Add Button Area
|
|
Constraint::Min(5), // Columns Table Area
|
|
Constraint::Min(5), // Indexes & Links Area
|
|
Constraint::Length(3), // Bottom: Save/Cancel Buttons
|
|
])
|
|
.split(inner_area);
|
|
|
|
top_info_area = main_chunks[0];
|
|
canvas_area = main_chunks[1];
|
|
add_button_area = main_chunks[2];
|
|
columns_area = main_chunks[3];
|
|
let indexes_links_area = main_chunks[4];
|
|
bottom_buttons_area = main_chunks[5];
|
|
|
|
// Split Indexes/Links Horizontally
|
|
let indexes_links_chunks = Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints([
|
|
Constraint::Percentage(50), // Indexes Table
|
|
Constraint::Percentage(50), // Links Table
|
|
])
|
|
.split(indexes_links_area);
|
|
indexes_area = indexes_links_chunks[0];
|
|
links_area = indexes_links_chunks[1];
|
|
|
|
// --- Top Info Rendering (Narrow - 1 line) ---
|
|
let top_info_chunks = Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints([
|
|
Constraint::Percentage(50),
|
|
Constraint::Percentage(50),
|
|
])
|
|
.split(top_info_area);
|
|
|
|
let profile_text = Paragraph::new(Span::styled(
|
|
format!("Profile: {}", add_table_state.profile_name),
|
|
theme.fg,
|
|
))
|
|
.alignment(Alignment::Left);
|
|
f.render_widget(profile_text, top_info_chunks[0]);
|
|
|
|
let table_name_text = Paragraph::new(Span::styled(
|
|
format!("Table: {}", add_table_state.table_name),
|
|
theme.fg,
|
|
))
|
|
.alignment(Alignment::Left);
|
|
f.render_widget(table_name_text, top_info_chunks[1]);
|
|
}
|
|
|
|
// --- Common Widget Rendering (Uses calculated areas) ---
|
|
|
|
// --- Columns Table Rendering ---
|
|
let columns_focused = matches!(add_table_state.current_focus, AddTableFocus::ColumnsTable | AddTableFocus::InsideColumnsTable);
|
|
let columns_border_style = if columns_focused {
|
|
Style::default().fg(theme.highlight)
|
|
} else {
|
|
Style::default().fg(theme.secondary)
|
|
};
|
|
let column_rows: Vec<Row<'_>> = add_table_state
|
|
.columns
|
|
.iter()
|
|
.map(|col_def| {
|
|
Row::new(vec![
|
|
Cell::from(if col_def.selected { "[*]" } else { "[ ]" }),
|
|
Cell::from(col_def.name.clone()),
|
|
Cell::from(col_def.data_type.clone()),
|
|
])
|
|
.style(Style::default().fg(theme.fg))
|
|
})
|
|
.collect();
|
|
let header_cells = ["Sel", "Name", "Type"]
|
|
.iter()
|
|
.map(|h| Cell::from(*h).style(Style::default().fg(theme.accent)));
|
|
let header = Row::new(header_cells).height(1).bottom_margin(1);
|
|
let columns_table = Table::new(
|
|
column_rows,
|
|
[ // Define constraints for 3 columns: Sel, Name, Type
|
|
Constraint::Length(5),
|
|
Constraint::Percentage(60),
|
|
Constraint::Percentage(35),
|
|
],
|
|
)
|
|
.header(header)
|
|
.block(
|
|
Block::default()
|
|
.title(Span::styled(" Columns ", theme.fg))
|
|
.title_alignment(Alignment::Center)
|
|
.borders(Borders::ALL)
|
|
.border_type(BorderType::Rounded)
|
|
.border_style(columns_border_style),
|
|
)
|
|
.row_highlight_style(
|
|
Style::default()
|
|
.add_modifier(Modifier::REVERSED)
|
|
.fg(theme.highlight),
|
|
)
|
|
.highlight_symbol(" > ");
|
|
f.render_stateful_widget(
|
|
columns_table,
|
|
columns_area,
|
|
&mut add_table_state.column_table_state,
|
|
);
|
|
|
|
// --- Canvas Rendering (Column Definition Input) ---
|
|
let _active_field_rect = render_canvas(
|
|
f,
|
|
canvas_area,
|
|
add_table_state,
|
|
&add_table_state.fields(),
|
|
&add_table_state.current_field(),
|
|
&add_table_state.inputs(),
|
|
theme,
|
|
is_edit_mode && focus_on_canvas_inputs,
|
|
highlight_state,
|
|
);
|
|
|
|
// --- Button Style Helpers ---
|
|
let get_button_style = |button_focus: AddTableFocus, current_focus| {
|
|
// Only handles text style (FG + Bold) now, no BG
|
|
let is_focused = current_focus == button_focus;
|
|
let base_style = Style::default().fg(if is_focused {
|
|
theme.highlight // Highlighted text color
|
|
} else {
|
|
theme.secondary // Normal text color
|
|
});
|
|
if is_focused {
|
|
base_style.add_modifier(Modifier::BOLD)
|
|
} else {
|
|
base_style
|
|
}
|
|
};
|
|
// Updated signature to accept bool and theme
|
|
let get_button_border_style = |is_focused: bool, theme: &Theme| {
|
|
if is_focused {
|
|
Style::default().fg(theme.highlight)
|
|
} else {
|
|
Style::default().fg(theme.secondary)
|
|
}
|
|
};
|
|
|
|
// --- Add Button Rendering ---
|
|
// Determine if the add button is focused
|
|
let is_add_button_focused = add_table_state.current_focus == AddTableFocus::AddColumnButton;
|
|
|
|
// Create the Add button Paragraph widget
|
|
let add_button = Paragraph::new(" Add ")
|
|
.style(get_button_style(AddTableFocus::AddColumnButton, add_table_state.current_focus)) // Use existing closure
|
|
.alignment(Alignment::Center)
|
|
.block(
|
|
Block::default()
|
|
.borders(Borders::ALL)
|
|
.border_type(BorderType::Rounded)
|
|
.border_style(get_button_border_style(is_add_button_focused, theme)), // Pass bool and theme
|
|
);
|
|
|
|
// Render the button in its designated area
|
|
f.render_widget(add_button, add_button_area);
|
|
|
|
// --- Indexes Table Rendering ---
|
|
let indexes_focused = matches!(add_table_state.current_focus, AddTableFocus::IndexesTable | AddTableFocus::InsideIndexesTable);
|
|
let indexes_border_style = if indexes_focused {
|
|
Style::default().fg(theme.highlight)
|
|
} else {
|
|
Style::default().fg(theme.secondary)
|
|
};
|
|
let index_rows: Vec<Row<'_>> = add_table_state
|
|
.indexes
|
|
.iter()
|
|
.map(|index_name| {
|
|
Row::new(vec![Cell::from(index_name.clone())])
|
|
.style(Style::default().fg(theme.fg))
|
|
})
|
|
.collect();
|
|
let index_header_cells = ["Column Name"]
|
|
.iter()
|
|
.map(|h| Cell::from(*h).style(Style::default().fg(theme.accent)));
|
|
let index_header = Row::new(index_header_cells).height(1).bottom_margin(1);
|
|
let indexes_table =
|
|
Table::new(index_rows, [Constraint::Percentage(100)])
|
|
.header(index_header)
|
|
.block(
|
|
Block::default()
|
|
.title(Span::styled(" Indexes ", theme.fg))
|
|
.title_alignment(Alignment::Center)
|
|
.borders(Borders::ALL)
|
|
.border_type(BorderType::Rounded)
|
|
.border_style(indexes_border_style),
|
|
)
|
|
.row_highlight_style(
|
|
Style::default()
|
|
.add_modifier(Modifier::REVERSED)
|
|
.fg(theme.highlight),
|
|
)
|
|
.highlight_symbol(" > ");
|
|
f.render_stateful_widget(
|
|
indexes_table,
|
|
indexes_area,
|
|
&mut add_table_state.index_table_state,
|
|
);
|
|
|
|
// --- Links Table Rendering ---
|
|
let links_focused = matches!(add_table_state.current_focus, AddTableFocus::LinksTable | AddTableFocus::InsideLinksTable);
|
|
let links_border_style = if links_focused {
|
|
Style::default().fg(theme.highlight)
|
|
} else {
|
|
Style::default().fg(theme.secondary)
|
|
};
|
|
let link_rows: Vec<Row<'_>> = add_table_state
|
|
.links
|
|
.iter()
|
|
.map(|link_def| {
|
|
Row::new(vec![
|
|
Cell::from(link_def.linked_table_name.clone()),
|
|
Cell::from(if link_def.is_required { "[X]" } else { "[ ]" }),
|
|
])
|
|
.style(Style::default().fg(theme.fg))
|
|
})
|
|
.collect();
|
|
let link_header_cells = ["Linked Table", "Selected"]
|
|
.iter()
|
|
.map(|h| Cell::from(*h).style(Style::default().fg(theme.accent)));
|
|
let link_header = Row::new(link_header_cells).height(1).bottom_margin(1);
|
|
let links_table =
|
|
Table::new(link_rows, [Constraint::Percentage(80), Constraint::Min(5)])
|
|
.header(link_header)
|
|
.block(
|
|
Block::default()
|
|
.title(Span::styled(" Links ", theme.fg))
|
|
.title_alignment(Alignment::Center)
|
|
.borders(Borders::ALL)
|
|
.border_type(BorderType::Rounded)
|
|
.border_style(links_border_style),
|
|
)
|
|
.row_highlight_style(
|
|
Style::default()
|
|
.add_modifier(Modifier::REVERSED)
|
|
.fg(theme.highlight),
|
|
)
|
|
.highlight_symbol(" > ");
|
|
f.render_stateful_widget(
|
|
links_table,
|
|
links_area,
|
|
&mut add_table_state.link_table_state,
|
|
);
|
|
|
|
// --- Save/Cancel Buttons Rendering ---
|
|
let bottom_button_chunks = Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints([
|
|
Constraint::Percentage(33), // Save Button
|
|
Constraint::Percentage(34), // Delete Button
|
|
Constraint::Percentage(33), // Cancel Button
|
|
])
|
|
.split(bottom_buttons_area);
|
|
|
|
let save_button = Paragraph::new(" Save table ")
|
|
.style(get_button_style(
|
|
AddTableFocus::SaveButton,
|
|
add_table_state.current_focus,
|
|
))
|
|
.alignment(Alignment::Center)
|
|
.block(
|
|
Block::default()
|
|
.borders(Borders::ALL)
|
|
.border_type(BorderType::Rounded)
|
|
.border_style(get_button_border_style(
|
|
add_table_state.current_focus == AddTableFocus::SaveButton, // Pass bool
|
|
theme,
|
|
)),
|
|
);
|
|
f.render_widget(save_button, bottom_button_chunks[0]);
|
|
|
|
let delete_button = Paragraph::new(" Delete Selected ")
|
|
.style(get_button_style(
|
|
AddTableFocus::DeleteSelectedButton,
|
|
add_table_state.current_focus,
|
|
))
|
|
.alignment(Alignment::Center)
|
|
.block(
|
|
Block::default()
|
|
.borders(Borders::ALL)
|
|
.border_type(BorderType::Rounded)
|
|
.border_style(get_button_border_style(
|
|
add_table_state.current_focus == AddTableFocus::DeleteSelectedButton, // Pass bool
|
|
theme,
|
|
)),
|
|
);
|
|
f.render_widget(delete_button, bottom_button_chunks[1]);
|
|
|
|
let cancel_button = Paragraph::new(" Cancel ")
|
|
.style(get_button_style(
|
|
AddTableFocus::CancelButton,
|
|
add_table_state.current_focus,
|
|
))
|
|
.alignment(Alignment::Center)
|
|
.block(
|
|
Block::default()
|
|
.borders(Borders::ALL)
|
|
.border_type(BorderType::Rounded)
|
|
.border_style(get_button_border_style(
|
|
add_table_state.current_focus == AddTableFocus::CancelButton, // Pass bool
|
|
theme,
|
|
)),
|
|
);
|
|
f.render_widget(cancel_button, bottom_button_chunks[2]);
|
|
|
|
// --- DIALOG ---
|
|
// Render the dialog overlay if it's active
|
|
if app_state.ui.dialog.dialog_show { // Use the passed-in app_state
|
|
dialog::render_dialog(
|
|
f,
|
|
f.area(), // Render over the whole frame area
|
|
theme,
|
|
&app_state.ui.dialog.dialog_title,
|
|
&app_state.ui.dialog.dialog_message,
|
|
&app_state.ui.dialog.dialog_buttons,
|
|
app_state.ui.dialog.dialog_active_button_index,
|
|
app_state.ui.dialog.is_loading,
|
|
);
|
|
}
|
|
}
|