Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57f789290d | ||
|
|
f34317e504 | ||
|
|
8159a84447 |
@@ -1,209 +1,180 @@
|
|||||||
// src/components/admin/add_table.rs
|
// src/components/admin/add_table.rs
|
||||||
use crate::config::colors::themes::Theme;
|
use crate::config::colors::themes::Theme;
|
||||||
use crate::state::{
|
|
||||||
app::state::AppState,
|
|
||||||
pages::add_table::{AddTableFocus, AddTableState},
|
|
||||||
};
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||||
style::{Modifier, Style, Stylize},
|
style::{Style, Stylize},
|
||||||
text::{Line, Span},
|
text::{Line, Span},
|
||||||
widgets::{
|
widgets::{Block, BorderType, Borders, Paragraph},
|
||||||
Block, BorderType, Borders, Cell, Paragraph, Row, Table, TableState,
|
|
||||||
},
|
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
use crate::state::app::state::AppState;
|
||||||
|
use crate::state::app::highlight::HighlightState;
|
||||||
|
use crate::state::pages::canvas_state::CanvasState;
|
||||||
|
use crate::components::handlers::canvas::render_canvas;
|
||||||
|
use crate::state::pages::add_table::AddTableState;
|
||||||
|
|
||||||
/// Renders the detailed page for adding a new table.
|
/// Renders a placeholder page for adding tables.
|
||||||
pub fn render_add_table(
|
pub fn render_add_table(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
app_state: &AppState,
|
_app_state: &AppState,
|
||||||
add_table_state: &mut AddTableState,
|
add_table_state: &mut AddTableState,
|
||||||
|
is_edit_mode: bool,
|
||||||
|
highlight_state: &HighlightState,
|
||||||
) {
|
) {
|
||||||
let focused_style = Style::default().fg(theme.highlight);
|
// Main block for the whole page
|
||||||
let unfocused_style = Style::default().fg(theme.border);
|
|
||||||
let base_style = Style::default().fg(theme.fg);
|
|
||||||
let header_style = Style::default().fg(theme.secondary);
|
|
||||||
let selected_row_style = Style::default().bg(theme.highlight).fg(theme.bg);
|
|
||||||
|
|
||||||
// --- Main Block ---
|
|
||||||
let main_block = Block::default()
|
let main_block = Block::default()
|
||||||
.title(" Create New Table ")
|
.title(" Add New Table ")
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_type(BorderType::Rounded)
|
.border_type(BorderType::Rounded)
|
||||||
.border_style(Style::default().fg(theme.accent))
|
.border_style(Style::default().fg(theme.border))
|
||||||
.style(Style::default().bg(theme.bg));
|
.style(Style::default().bg(theme.bg));
|
||||||
let inner_area = main_block.inner(area);
|
let inner_area = main_block.inner(area);
|
||||||
f.render_widget(main_block, area);
|
f.render_widget(main_block, area);
|
||||||
|
|
||||||
// --- Layout ---
|
// Split the inner area horizontally: Left info pane | Right input/action pane
|
||||||
let constraints = [
|
let horizontal_chunks = Layout::default()
|
||||||
Constraint::Length(1), // Profile line
|
.direction(Direction::Horizontal)
|
||||||
Constraint::Length(1), // Table Name line
|
.constraints([
|
||||||
Constraint::Length(1), // Spacer
|
Constraint::Percentage(50), // Left Pane
|
||||||
Constraint::Length(1), // Columns Header
|
Constraint::Percentage(50), // Right Pane
|
||||||
Constraint::Min(3), // Columns Table (at least 3 rows visible)
|
].as_ref())
|
||||||
Constraint::Length(1), // Spacer
|
|
||||||
Constraint::Length(1), // Indexes Header
|
|
||||||
Constraint::Min(2), // Indexes Table
|
|
||||||
Constraint::Length(1), // Spacer
|
|
||||||
Constraint::Length(1), // Links Header
|
|
||||||
Constraint::Min(3), // Links Table
|
|
||||||
Constraint::Length(1), // Spacer
|
|
||||||
Constraint::Length(1), // Action Buttons
|
|
||||||
];
|
|
||||||
let chunks = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints(constraints)
|
|
||||||
.margin(1) // Add margin inside the main block
|
|
||||||
.split(inner_area);
|
.split(inner_area);
|
||||||
|
|
||||||
// --- Helper: Get Border Style based on Focus ---
|
let left_pane = horizontal_chunks[0];
|
||||||
let get_border_style = |focus: AddTableFocus| {
|
let right_pane = horizontal_chunks[1];
|
||||||
if add_table_state.current_focus == focus {
|
|
||||||
focused_style
|
|
||||||
} else {
|
|
||||||
unfocused_style
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- 1. Profile Line ---
|
// --- Left Pane ---
|
||||||
// TODO: Fetch actual selected profile if needed, using app_state.selected_profile or admin_state
|
let left_vertical_chunks = Layout::default()
|
||||||
let profile_text = format!("Profile: {}", add_table_state.profile_name);
|
.direction(Direction::Vertical)
|
||||||
f.render_widget(Paragraph::new(profile_text).style(base_style), chunks[0]);
|
.constraints([
|
||||||
|
Constraint::Length(3), // Profile & Table Name header
|
||||||
|
Constraint::Min(5), // Columns section (expandable)
|
||||||
|
Constraint::Length(1), // Separator
|
||||||
|
Constraint::Min(3), // Indexes section (expandable)
|
||||||
|
Constraint::Length(1), // Separator
|
||||||
|
Constraint::Min(3), // Links section (expandable)
|
||||||
|
].as_ref())
|
||||||
|
.split(left_pane);
|
||||||
|
|
||||||
// --- 2. Table Name Line ---
|
// Profile & Table Name section
|
||||||
let table_name_block = Block::default()
|
let profile_text = Paragraph::new(vec![
|
||||||
.borders(Borders::BOTTOM)
|
Line::from(Span::styled("profile: default", theme.fg)), // Placeholder
|
||||||
.border_style(get_border_style(AddTableFocus::TableName));
|
Line::from(Span::styled("table name: [tablename]", theme.fg)), // Placeholder
|
||||||
// Basic rendering for now, cursor needs event handling logic
|
])
|
||||||
let table_name_text = format!("Table Name: [ {} ]", add_table_state.table_name);
|
.block(
|
||||||
let table_name_paragraph = Paragraph::new(table_name_text)
|
Block::default()
|
||||||
.style(base_style)
|
.borders(Borders::BOTTOM)
|
||||||
.block(table_name_block);
|
.border_style(Style::default().fg(theme.secondary)),
|
||||||
f.render_widget(table_name_paragraph, chunks[1]);
|
);
|
||||||
|
f.render_widget(profile_text, left_vertical_chunks[0]);
|
||||||
|
|
||||||
// --- 4. Columns Header ---
|
// Columns section
|
||||||
let columns_header = Paragraph::new(Line::from(vec![
|
let columns_text = Paragraph::new(vec![
|
||||||
Span::styled(" Name ", header_style),
|
Line::from(Span::styled("Name Type", theme.accent)), // Header
|
||||||
Span::raw("| "),
|
])
|
||||||
Span::styled("Type ", header_style),
|
.block(Block::default().title(Span::styled(" Columns ", theme.fg)));
|
||||||
Span::raw("| "),
|
f.render_widget(columns_text, left_vertical_chunks[1]);
|
||||||
Span::styled(" <- Add Column (Ctrl+A)", header_style.add_modifier(Modifier::ITALIC)),
|
|
||||||
]));
|
|
||||||
f.render_widget(columns_header, chunks[3]);
|
|
||||||
|
|
||||||
// --- 5. Columns Table ---
|
// Indexes section
|
||||||
let columns_block = Block::default()
|
let indexes_text = Paragraph::new(vec![
|
||||||
.borders(Borders::TOP | Borders::BOTTOM) // Only top/bottom for visual separation
|
Line::from(Span::styled("Column name", theme.accent)), // Header
|
||||||
.border_style(get_border_style(AddTableFocus::Columns));
|
])
|
||||||
let columns_table_area = columns_block.inner(chunks[4]);
|
.block(
|
||||||
f.render_widget(columns_block, chunks[4]);
|
Block::default()
|
||||||
|
.title(Span::styled(" Indexes ", theme.fg))
|
||||||
|
.borders(Borders::TOP) // Separator from Columns
|
||||||
|
.border_style(Style::default().fg(theme.secondary)),
|
||||||
|
);
|
||||||
|
f.render_widget(indexes_text, left_vertical_chunks[3]);
|
||||||
|
|
||||||
let column_rows = add_table_state.columns.iter().map(|col| {
|
// Links section
|
||||||
Row::new(vec![
|
let links_text = Paragraph::new(vec![
|
||||||
Cell::from(col.name.as_str()),
|
Line::from(Span::styled("Linked table Required", theme.accent)), // Header
|
||||||
Cell::from(col.data_type.as_str()),
|
])
|
||||||
])
|
.block(
|
||||||
.style(base_style)
|
Block::default()
|
||||||
});
|
.title(Span::styled(" Links ", theme.fg))
|
||||||
let column_widths = [Constraint::Percentage(50), Constraint::Percentage(50)];
|
.borders(Borders::TOP) // Separator from Indexes
|
||||||
let columns_table = Table::new(column_rows, column_widths)
|
.border_style(Style::default().fg(theme.secondary)),
|
||||||
.highlight_style(selected_row_style)
|
);
|
||||||
.highlight_symbol("* "); // Indicate selection
|
f.render_widget(links_text, left_vertical_chunks[5]);
|
||||||
f.render_stateful_widget(
|
|
||||||
columns_table,
|
// --- Right Pane ---
|
||||||
columns_table_area,
|
let right_vertical_chunks = Layout::default()
|
||||||
&mut add_table_state.column_table_state,
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Length(5), // Area for render_canvas (3 fields + 2 border)
|
||||||
|
Constraint::Length(3), // Add Button Area
|
||||||
|
Constraint::Min(1), // Spacer
|
||||||
|
Constraint::Length(3), // Save/Cancel buttons area
|
||||||
|
].as_ref())
|
||||||
|
.split(right_pane);
|
||||||
|
|
||||||
|
let canvas_area = right_vertical_chunks[0];
|
||||||
|
let add_button_area = right_vertical_chunks[1];
|
||||||
|
let bottom_buttons_area = right_vertical_chunks[3];
|
||||||
|
|
||||||
|
// --- Use render_canvas for Inputs ---
|
||||||
|
let _active_field_rect = render_canvas(
|
||||||
|
f,
|
||||||
|
canvas_area,
|
||||||
|
add_table_state,
|
||||||
|
&[
|
||||||
|
"Table name",
|
||||||
|
"Name",
|
||||||
|
"Type",
|
||||||
|
],
|
||||||
|
&add_table_state.current_field(),
|
||||||
|
&add_table_state.inputs().iter().map(|s| *s).collect::<Vec<&String>>(),
|
||||||
|
theme,
|
||||||
|
is_edit_mode,
|
||||||
|
highlight_state,
|
||||||
);
|
);
|
||||||
|
|
||||||
// --- 7. Indexes Header ---
|
// Add Button (Placeholder)
|
||||||
let indexes_header = Paragraph::new(Line::from(vec![
|
let add_button = Paragraph::new(" Add ")
|
||||||
Span::styled(" Column Name ", header_style),
|
.style(Style::default().fg(theme.secondary))
|
||||||
Span::raw("| "),
|
.alignment(Alignment::Center)
|
||||||
Span::styled(" <- Add Index (Ctrl+I), Remove (Ctrl+X)", header_style.add_modifier(Modifier::ITALIC)),
|
.block(
|
||||||
]));
|
Block::default()
|
||||||
f.render_widget(indexes_header, chunks[6]);
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.border_style(Style::default().fg(theme.secondary)),
|
||||||
|
);
|
||||||
|
f.render_widget(add_button, add_button_area);
|
||||||
|
|
||||||
// --- 8. Indexes Table ---
|
// Bottom Buttons Area (Save, Cancel)
|
||||||
let indexes_block = Block::default()
|
let bottom_button_chunks = Layout::default()
|
||||||
.borders(Borders::TOP | Borders::BOTTOM)
|
|
||||||
.border_style(get_border_style(AddTableFocus::Indexes));
|
|
||||||
let indexes_table_area = indexes_block.inner(chunks[7]);
|
|
||||||
f.render_widget(indexes_block, chunks[7]);
|
|
||||||
|
|
||||||
let index_rows = add_table_state.indexes.iter().map(|idx_col_name| {
|
|
||||||
Row::new(vec![Cell::from(idx_col_name.as_str())]).style(base_style)
|
|
||||||
});
|
|
||||||
let index_widths = [Constraint::Percentage(100)];
|
|
||||||
let indexes_table = Table::new(index_rows, index_widths)
|
|
||||||
.highlight_style(selected_row_style)
|
|
||||||
.highlight_symbol("* ");
|
|
||||||
f.render_stateful_widget(
|
|
||||||
indexes_table,
|
|
||||||
indexes_table_area,
|
|
||||||
&mut add_table_state.index_table_state,
|
|
||||||
);
|
|
||||||
|
|
||||||
// --- 10. Links Header ---
|
|
||||||
let links_header = Paragraph::new(Line::from(vec![
|
|
||||||
Span::styled(" Linked Table ", header_style),
|
|
||||||
Span::raw("| "),
|
|
||||||
Span::styled("Required? ", header_style),
|
|
||||||
Span::raw("| "),
|
|
||||||
Span::styled(" <- Toggle Required (Space)", header_style.add_modifier(Modifier::ITALIC)),
|
|
||||||
]));
|
|
||||||
f.render_widget(links_header, chunks[9]);
|
|
||||||
|
|
||||||
// --- 11. Links Table ---
|
|
||||||
let links_block = Block::default()
|
|
||||||
.borders(Borders::TOP | Borders::BOTTOM)
|
|
||||||
.border_style(get_border_style(AddTableFocus::Links));
|
|
||||||
let links_table_area = links_block.inner(chunks[10]);
|
|
||||||
f.render_widget(links_block, chunks[10]);
|
|
||||||
|
|
||||||
let link_rows = add_table_state.links.iter().map(|link| {
|
|
||||||
let required_text = if link.is_required { "[X] Yes" } else { "[ ] No " }; // Pad No for alignment
|
|
||||||
Row::new(vec![
|
|
||||||
Cell::from(link.linked_table_name.as_str()),
|
|
||||||
Cell::from(required_text),
|
|
||||||
])
|
|
||||||
.style(base_style)
|
|
||||||
});
|
|
||||||
let link_widths = [Constraint::Percentage(70), Constraint::Percentage(30)];
|
|
||||||
let links_table = Table::new(link_rows, link_widths)
|
|
||||||
.highlight_style(selected_row_style)
|
|
||||||
.highlight_symbol("* ");
|
|
||||||
f.render_stateful_widget(
|
|
||||||
links_table,
|
|
||||||
links_table_area,
|
|
||||||
&mut add_table_state.link_table_state,
|
|
||||||
);
|
|
||||||
|
|
||||||
// --- 13. Action Buttons ---
|
|
||||||
let button_layout = Layout::default()
|
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
.constraints([
|
||||||
.split(chunks[12]);
|
Constraint::Percentage(50), // Save Button
|
||||||
|
Constraint::Percentage(50), // Cancel Button
|
||||||
|
].as_ref())
|
||||||
|
.split(bottom_buttons_area);
|
||||||
|
|
||||||
let save_button = Paragraph::new("[ Save Table ]")
|
// Save Button (Placeholder)
|
||||||
|
let save_button = Paragraph::new(" Save table ")
|
||||||
|
.style(Style::default().fg(theme.secondary))
|
||||||
.alignment(Alignment::Center)
|
.alignment(Alignment::Center)
|
||||||
.style(if add_table_state.current_focus == AddTableFocus::SaveButton {
|
.block(
|
||||||
selected_row_style // Use selected style for focused button
|
Block::default()
|
||||||
} else {
|
.borders(Borders::ALL)
|
||||||
base_style
|
.border_type(BorderType::Rounded)
|
||||||
});
|
.border_style(Style::default().fg(theme.secondary)),
|
||||||
let cancel_button = Paragraph::new("[ Cancel ]")
|
);
|
||||||
.alignment(Alignment::Center)
|
f.render_widget(save_button, bottom_button_chunks[0]);
|
||||||
.style(if add_table_state.current_focus == AddTableFocus::CancelButton {
|
|
||||||
selected_row_style
|
|
||||||
} else {
|
|
||||||
base_style
|
|
||||||
});
|
|
||||||
|
|
||||||
f.render_widget(save_button, button_layout[0]);
|
// Cancel Button (Placeholder)
|
||||||
f.render_widget(cancel_button, button_layout[1]);
|
let cancel_button = Paragraph::new(" Cancel ")
|
||||||
|
.style(Style::default().fg(theme.secondary))
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.border_style(Style::default().fg(theme.secondary)),
|
||||||
|
);
|
||||||
|
f.render_widget(cancel_button, bottom_button_chunks[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
// src/functions/modes/navigation.rs
|
// src/functions/modes/navigation.rs
|
||||||
|
|
||||||
pub mod admin_nav;
|
pub mod admin_nav;
|
||||||
|
pub mod add_table_nav;
|
||||||
|
|||||||
260
client/src/functions/modes/navigation/add_table_nav.rs
Normal file
260
client/src/functions/modes/navigation/add_table_nav.rs
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
// src/functions/modes/navigation/add_table_nav.rs
|
||||||
|
use crate::config::binds::config::Config;
|
||||||
|
use crate::state::{
|
||||||
|
app::state::AppState,
|
||||||
|
pages::add_table::{AddTableFocus, AddTableState},
|
||||||
|
};
|
||||||
|
use crossterm::event::{KeyEvent};
|
||||||
|
use ratatui::widgets::TableState; // Import TableState
|
||||||
|
|
||||||
|
/// Handles navigation events specifically for the Add Table view.
|
||||||
|
/// Returns true if the event was handled, false otherwise.
|
||||||
|
pub fn handle_add_table_navigation(
|
||||||
|
key: KeyEvent,
|
||||||
|
config: &Config,
|
||||||
|
_app_state: &AppState, // Keep for potential future use (e.g., checking permissions)
|
||||||
|
add_table_state: &mut AddTableState,
|
||||||
|
command_message: &mut String,
|
||||||
|
) -> bool {
|
||||||
|
let action = config.get_general_action(key.code, key.modifiers);
|
||||||
|
let current_focus = add_table_state.current_focus;
|
||||||
|
let mut handled = true; // Assume handled unless logic determines otherwise
|
||||||
|
|
||||||
|
match action.as_deref() {
|
||||||
|
// --- Vertical Navigation (Up/Down) ---
|
||||||
|
Some("move_up") => {
|
||||||
|
let mut new_focus = current_focus; // Start with current focus
|
||||||
|
match current_focus {
|
||||||
|
AddTableFocus::InputTableName => new_focus = AddTableFocus::CancelButton, // Wrap top
|
||||||
|
AddTableFocus::InputColumnName => new_focus = AddTableFocus::InputTableName,
|
||||||
|
AddTableFocus::InputColumnType => new_focus = AddTableFocus::InputColumnName,
|
||||||
|
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::InputColumnType,
|
||||||
|
AddTableFocus::ColumnsTable => {
|
||||||
|
if !navigate_table_up(&mut add_table_state.column_table_state, add_table_state.columns.len()) {
|
||||||
|
new_focus = AddTableFocus::AddColumnButton; // Move focus up if at table top
|
||||||
|
}
|
||||||
|
// Keep focus on table while navigating within it
|
||||||
|
}
|
||||||
|
AddTableFocus::IndexesTable => {
|
||||||
|
if !navigate_table_up(&mut add_table_state.index_table_state, add_table_state.indexes.len()) {
|
||||||
|
new_focus = AddTableFocus::ColumnsTable; // Move focus up
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddTableFocus::LinksTable => {
|
||||||
|
if !navigate_table_up(&mut add_table_state.link_table_state, add_table_state.links.len()) {
|
||||||
|
new_focus = AddTableFocus::IndexesTable; // Move focus up
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddTableFocus::SaveButton => new_focus = AddTableFocus::LinksTable,
|
||||||
|
AddTableFocus::CancelButton => new_focus = AddTableFocus::SaveButton,
|
||||||
|
}
|
||||||
|
add_table_state.current_focus = new_focus;
|
||||||
|
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
|
||||||
|
}
|
||||||
|
Some("move_down") => {
|
||||||
|
let mut new_focus = current_focus; // Start with current focus
|
||||||
|
match current_focus {
|
||||||
|
AddTableFocus::InputTableName => new_focus = AddTableFocus::InputColumnName,
|
||||||
|
AddTableFocus::InputColumnName => new_focus = AddTableFocus::InputColumnType,
|
||||||
|
AddTableFocus::InputColumnType => new_focus = AddTableFocus::AddColumnButton,
|
||||||
|
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::ColumnsTable,
|
||||||
|
AddTableFocus::ColumnsTable => {
|
||||||
|
if !navigate_table_down(&mut add_table_state.column_table_state, add_table_state.columns.len()) {
|
||||||
|
new_focus = AddTableFocus::IndexesTable; // Move focus down if at table bottom
|
||||||
|
}
|
||||||
|
// Keep focus on table while navigating within it
|
||||||
|
}
|
||||||
|
AddTableFocus::IndexesTable => {
|
||||||
|
if !navigate_table_down(&mut add_table_state.index_table_state, add_table_state.indexes.len()) {
|
||||||
|
new_focus = AddTableFocus::LinksTable; // Move focus down
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddTableFocus::LinksTable => {
|
||||||
|
if !navigate_table_down(&mut add_table_state.link_table_state, add_table_state.links.len()) {
|
||||||
|
new_focus = AddTableFocus::SaveButton; // Move focus down
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddTableFocus::SaveButton => new_focus = AddTableFocus::CancelButton,
|
||||||
|
AddTableFocus::CancelButton => new_focus = AddTableFocus::InputTableName, // Wrap bottom
|
||||||
|
}
|
||||||
|
add_table_state.current_focus = new_focus;
|
||||||
|
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Horizontal Navigation (Left/Right) ---
|
||||||
|
Some("next_option") => { // 'l' or Right
|
||||||
|
add_table_state.current_focus = match current_focus {
|
||||||
|
AddTableFocus::SaveButton => AddTableFocus::CancelButton,
|
||||||
|
_ => current_focus, // No change for others yet
|
||||||
|
};
|
||||||
|
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
|
||||||
|
}
|
||||||
|
Some("previous_option") => { // 'h' or Left
|
||||||
|
add_table_state.current_focus = match current_focus {
|
||||||
|
AddTableFocus::CancelButton => AddTableFocus::SaveButton,
|
||||||
|
_ => current_focus, // No change for others yet
|
||||||
|
};
|
||||||
|
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Tab / Shift+Tab Navigation ---
|
||||||
|
Some("next_field") => { // Tab
|
||||||
|
add_table_state.current_focus = match current_focus {
|
||||||
|
AddTableFocus::InputTableName => AddTableFocus::InputColumnName,
|
||||||
|
AddTableFocus::InputColumnName => AddTableFocus::InputColumnType,
|
||||||
|
AddTableFocus::InputColumnType => AddTableFocus::AddColumnButton,
|
||||||
|
AddTableFocus::AddColumnButton => AddTableFocus::ColumnsTable,
|
||||||
|
AddTableFocus::ColumnsTable => AddTableFocus::IndexesTable,
|
||||||
|
AddTableFocus::IndexesTable => AddTableFocus::LinksTable,
|
||||||
|
AddTableFocus::LinksTable => AddTableFocus::SaveButton,
|
||||||
|
AddTableFocus::SaveButton => AddTableFocus::CancelButton,
|
||||||
|
AddTableFocus::CancelButton => AddTableFocus::InputTableName, // Wrap
|
||||||
|
};
|
||||||
|
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
|
||||||
|
}
|
||||||
|
Some("prev_field") => { // Shift+Tab
|
||||||
|
add_table_state.current_focus = match current_focus {
|
||||||
|
AddTableFocus::InputTableName => AddTableFocus::CancelButton, // Wrap
|
||||||
|
AddTableFocus::InputColumnName => AddTableFocus::InputTableName,
|
||||||
|
AddTableFocus::InputColumnType => AddTableFocus::InputColumnName,
|
||||||
|
AddTableFocus::AddColumnButton => AddTableFocus::InputColumnType,
|
||||||
|
AddTableFocus::ColumnsTable => AddTableFocus::AddColumnButton,
|
||||||
|
AddTableFocus::IndexesTable => AddTableFocus::ColumnsTable,
|
||||||
|
AddTableFocus::LinksTable => AddTableFocus::IndexesTable,
|
||||||
|
AddTableFocus::SaveButton => AddTableFocus::LinksTable,
|
||||||
|
AddTableFocus::CancelButton => AddTableFocus::SaveButton,
|
||||||
|
};
|
||||||
|
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Selection ---
|
||||||
|
Some("select") => {
|
||||||
|
match current_focus {
|
||||||
|
AddTableFocus::AddColumnButton => {
|
||||||
|
*command_message = "Action: Add Column (Not Implemented)".to_string();
|
||||||
|
// TODO: Implement logic to add column based on inputs
|
||||||
|
// Clear input fields, add to columns list, mark unsaved changes
|
||||||
|
// add_table_state.add_column(); // Example method call
|
||||||
|
}
|
||||||
|
AddTableFocus::SaveButton => {
|
||||||
|
*command_message = "Action: Save Table (Not Implemented)".to_string();
|
||||||
|
// TODO: Implement logic to save table (e.g., call API)
|
||||||
|
// Mark changes as saved
|
||||||
|
// add_table_state.save_table(); // Example method call
|
||||||
|
}
|
||||||
|
AddTableFocus::CancelButton => {
|
||||||
|
*command_message = "Action: Cancel Add Table".to_string();
|
||||||
|
// TODO: Implement logic to navigate back (e.g., update AppView history)
|
||||||
|
// Maybe show a confirmation dialog if there are unsaved changes
|
||||||
|
// buffer_state.go_back(); // Example call
|
||||||
|
}
|
||||||
|
// Selecting input fields usually means entering Edit mode (handled elsewhere)
|
||||||
|
// Selecting tables might mean focusing on them for editing/deletion (TODO)
|
||||||
|
AddTableFocus::ColumnsTable => {
|
||||||
|
if let Some(index) = add_table_state.column_table_state.selected() {
|
||||||
|
*command_message = format!("Selected column index {}", index);
|
||||||
|
// TODO: Add logic for editing/deleting selected column
|
||||||
|
} else {
|
||||||
|
*command_message = "No column selected".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddTableFocus::IndexesTable => {
|
||||||
|
if let Some(index) = add_table_state.index_table_state.selected() {
|
||||||
|
*command_message = format!("Selected index index {}", index);
|
||||||
|
// TODO: Add logic for editing/deleting selected index
|
||||||
|
} else {
|
||||||
|
*command_message = "No index selected".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddTableFocus::LinksTable => {
|
||||||
|
if let Some(index) = add_table_state.link_table_state.selected() {
|
||||||
|
*command_message = format!("Selected link index {}", index);
|
||||||
|
// TODO: Add logic for editing/deleting selected link
|
||||||
|
} else {
|
||||||
|
*command_message = "No link selected".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// For InputTableName, InputColumnName, InputColumnType,
|
||||||
|
// the main event loop should handle 'select' by potentially
|
||||||
|
// switching to Edit mode if not already in it.
|
||||||
|
// We don't need specific logic here for that.
|
||||||
|
*command_message = format!("Select on {:?}", current_focus);
|
||||||
|
handled = false; // Let main loop handle edit mode toggle maybe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Other General Keys (Ignore for add_table nav) ---
|
||||||
|
Some("toggle_sidebar") | Some("toggle_buffer_list") => {
|
||||||
|
handled = false; // Let global handler manage these
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- No matching action ---
|
||||||
|
_ => handled = false, // Event not handled by add_table navigation
|
||||||
|
}
|
||||||
|
|
||||||
|
// If focus changed TO a table, select the first row if nothing is selected
|
||||||
|
if handled && current_focus != add_table_state.current_focus {
|
||||||
|
match add_table_state.current_focus {
|
||||||
|
AddTableFocus::ColumnsTable if add_table_state.column_table_state.selected().is_none() && !add_table_state.columns.is_empty() => {
|
||||||
|
add_table_state.column_table_state.select(Some(0));
|
||||||
|
}
|
||||||
|
AddTableFocus::IndexesTable if add_table_state.index_table_state.selected().is_none() && !add_table_state.indexes.is_empty() => {
|
||||||
|
add_table_state.index_table_state.select(Some(0));
|
||||||
|
}
|
||||||
|
AddTableFocus::LinksTable if add_table_state.link_table_state.selected().is_none() && !add_table_state.links.is_empty() => {
|
||||||
|
add_table_state.link_table_state.select(Some(0));
|
||||||
|
}
|
||||||
|
_ => {} // No action needed for other focus states
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handled
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Helper function for navigating up within a table state
|
||||||
|
// Returns true if navigation happened within the table, false if it reached the top
|
||||||
|
fn navigate_table_up(table_state: &mut TableState, item_count: usize) -> bool {
|
||||||
|
if item_count == 0 { return false; } // Cannot navigate empty table
|
||||||
|
let current_selection = table_state.selected();
|
||||||
|
match current_selection {
|
||||||
|
Some(index) => {
|
||||||
|
if index > 0 {
|
||||||
|
table_state.select(Some(index - 1));
|
||||||
|
true // Moved up within table
|
||||||
|
} else {
|
||||||
|
false // Was at the top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// If nothing selected, moving up could select the last item
|
||||||
|
table_state.select(Some(item_count - 1));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function for navigating down within a table state
|
||||||
|
// Returns true if navigation happened within the table, false if it reached the bottom
|
||||||
|
fn navigate_table_down(table_state: &mut TableState, item_count: usize) -> bool {
|
||||||
|
if item_count == 0 { return false; } // Cannot navigate empty table
|
||||||
|
let current_selection = table_state.selected();
|
||||||
|
match current_selection {
|
||||||
|
Some(index) => {
|
||||||
|
if index < item_count - 1 {
|
||||||
|
table_state.select(Some(index + 1));
|
||||||
|
true // Moved down within table
|
||||||
|
} else {
|
||||||
|
false // Was at the bottom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// If nothing selected, moving down could select the first item
|
||||||
|
table_state.select(Some(0));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ use crate::modes::{
|
|||||||
highlight::highlight,
|
highlight::highlight,
|
||||||
general::{navigation, dialog},
|
general::{navigation, dialog},
|
||||||
};
|
};
|
||||||
use crate::functions::modes::navigation::admin_nav;
|
use crate::functions::modes::navigation::{admin_nav, add_table_nav};
|
||||||
use crate::config::binds::key_sequences::KeySequenceTracker;
|
use crate::config::binds::key_sequences::KeySequenceTracker;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -175,6 +175,21 @@ impl EventHandler {
|
|||||||
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// --- Add Table Page Navigation ---
|
||||||
|
if app_state.ui.show_add_table {
|
||||||
|
if let Some(action) = config.get_general_action(key.code, key.modifiers) {
|
||||||
|
if add_table_nav::handle_add_table_navigation(
|
||||||
|
key,
|
||||||
|
config,
|
||||||
|
app_state,
|
||||||
|
&mut admin_state.add_table_state,
|
||||||
|
&mut self.command_message,
|
||||||
|
|
||||||
|
) {
|
||||||
|
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let nav_outcome = navigation::handle_navigation_event(
|
let nav_outcome = navigation::handle_navigation_event(
|
||||||
key,
|
key,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ impl ModeManager {
|
|||||||
return AppMode::Highlight;
|
return AppMode::Highlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
if app_state.ui.focus_outside_canvas {
|
if app_state.ui.focus_outside_canvas || app_state.ui.show_add_table{
|
||||||
return AppMode::General;
|
return AppMode::General;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// src/state/pages/add_table.rs
|
// src/state/pages/add_table.rs
|
||||||
|
use crate::state::pages::canvas_state::CanvasState;
|
||||||
use ratatui::widgets::TableState;
|
use ratatui::widgets::TableState;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -16,10 +17,15 @@ pub struct LinkDefinition {
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
pub enum AddTableFocus {
|
pub enum AddTableFocus {
|
||||||
#[default]
|
#[default]
|
||||||
TableName,
|
InputTableName, // Field 0 for CanvasState
|
||||||
Columns,
|
InputColumnName, // Field 1 for CanvasState
|
||||||
Indexes,
|
InputColumnType, // Field 2 for CanvasState
|
||||||
Links,
|
AddColumnButton,
|
||||||
|
// Result Tables
|
||||||
|
ColumnsTable,
|
||||||
|
IndexesTable,
|
||||||
|
LinksTable,
|
||||||
|
// Buttons
|
||||||
SaveButton,
|
SaveButton,
|
||||||
CancelButton,
|
CancelButton,
|
||||||
}
|
}
|
||||||
@@ -28,6 +34,8 @@ pub enum AddTableFocus {
|
|||||||
pub struct AddTableState {
|
pub struct AddTableState {
|
||||||
pub profile_name: String,
|
pub profile_name: String,
|
||||||
pub table_name: String,
|
pub table_name: String,
|
||||||
|
pub column_name_input: String,
|
||||||
|
pub column_type_input: String,
|
||||||
pub columns: Vec<ColumnDefinition>,
|
pub columns: Vec<ColumnDefinition>,
|
||||||
pub indexes: Vec<String>,
|
pub indexes: Vec<String>,
|
||||||
pub links: Vec<LinkDefinition>,
|
pub links: Vec<LinkDefinition>,
|
||||||
@@ -36,6 +44,9 @@ pub struct AddTableState {
|
|||||||
pub index_table_state: TableState,
|
pub index_table_state: TableState,
|
||||||
pub link_table_state: TableState,
|
pub link_table_state: TableState,
|
||||||
pub table_name_cursor_pos: usize,
|
pub table_name_cursor_pos: usize,
|
||||||
|
pub column_name_cursor_pos: usize,
|
||||||
|
pub column_type_cursor_pos: usize,
|
||||||
|
pub has_unsaved_changes: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AddTableState {
|
impl Default for AddTableState {
|
||||||
@@ -43,7 +54,9 @@ impl Default for AddTableState {
|
|||||||
// Initialize with some dummy data for demonstration
|
// Initialize with some dummy data for demonstration
|
||||||
AddTableState {
|
AddTableState {
|
||||||
profile_name: "default".to_string(), // Should be set dynamically
|
profile_name: "default".to_string(), // Should be set dynamically
|
||||||
table_name: "new_table".to_string(),
|
table_name: String::new(), // Start empty
|
||||||
|
column_name_input: String::new(),
|
||||||
|
column_type_input: String::new(),
|
||||||
columns: vec![
|
columns: vec![
|
||||||
ColumnDefinition { name: "id".to_string(), data_type: "INTEGER".to_string() },
|
ColumnDefinition { name: "id".to_string(), data_type: "INTEGER".to_string() },
|
||||||
ColumnDefinition { name: "name".to_string(), data_type: "TEXT".to_string() },
|
ColumnDefinition { name: "name".to_string(), data_type: "TEXT".to_string() },
|
||||||
@@ -53,16 +66,107 @@ impl Default for AddTableState {
|
|||||||
LinkDefinition { linked_table_name: "related_table".to_string(), is_required: true },
|
LinkDefinition { linked_table_name: "related_table".to_string(), is_required: true },
|
||||||
LinkDefinition { linked_table_name: "another_table".to_string(), is_required: false },
|
LinkDefinition { linked_table_name: "another_table".to_string(), is_required: false },
|
||||||
],
|
],
|
||||||
current_focus: AddTableFocus::TableName,
|
current_focus: AddTableFocus::InputTableName,
|
||||||
column_table_state: TableState::default().with_selected(0),
|
column_table_state: TableState::default().with_selected(0),
|
||||||
index_table_state: TableState::default().with_selected(0),
|
index_table_state: TableState::default().with_selected(0),
|
||||||
link_table_state: TableState::default().with_selected(0),
|
link_table_state: TableState::default().with_selected(0),
|
||||||
table_name_cursor_pos: 0,
|
table_name_cursor_pos: 0,
|
||||||
|
column_name_cursor_pos: 0,
|
||||||
|
column_type_cursor_pos: 0,
|
||||||
|
has_unsaved_changes: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddTableState {
|
impl AddTableState {
|
||||||
|
const INPUT_FIELD_COUNT: usize = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implement CanvasState for the input fields
|
||||||
|
impl CanvasState for AddTableState {
|
||||||
|
fn current_field(&self) -> usize {
|
||||||
|
match self.current_focus {
|
||||||
|
AddTableFocus::InputTableName => 0,
|
||||||
|
AddTableFocus::InputColumnName => 1,
|
||||||
|
AddTableFocus::InputColumnType => 2,
|
||||||
|
// If focus is elsewhere, default to the first field for canvas rendering logic
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_cursor_pos(&self) -> usize {
|
||||||
|
match self.current_focus {
|
||||||
|
AddTableFocus::InputTableName => self.table_name_cursor_pos,
|
||||||
|
AddTableFocus::InputColumnName => self.column_name_cursor_pos,
|
||||||
|
AddTableFocus::InputColumnType => self.column_type_cursor_pos,
|
||||||
|
_ => 0, // Default if focus is not on an input field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_unsaved_changes(&self) -> bool {
|
||||||
|
self.has_unsaved_changes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inputs(&self) -> Vec<&String> {
|
||||||
|
vec![&self.table_name, &self.column_name_input, &self.column_type_input]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_input(&self) -> &str {
|
||||||
|
match self.current_focus {
|
||||||
|
AddTableFocus::InputTableName => &self.table_name,
|
||||||
|
AddTableFocus::InputColumnName => &self.column_name_input,
|
||||||
|
AddTableFocus::InputColumnType => &self.column_type_input,
|
||||||
|
_ => "", // Should not happen if called correctly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_input_mut(&mut self) -> &mut String {
|
||||||
|
match self.current_focus {
|
||||||
|
AddTableFocus::InputTableName => &mut self.table_name,
|
||||||
|
AddTableFocus::InputColumnName => &mut self.column_name_input,
|
||||||
|
AddTableFocus::InputColumnType => &mut self.column_type_input,
|
||||||
|
// This case needs careful handling. If focus isn't on an input,
|
||||||
|
// which mutable string should we return? Returning the first one
|
||||||
|
// might be unexpected. Consider panicking or returning Option if this state is invalid.
|
||||||
|
// For now, returning the first field to avoid panics during rendering.
|
||||||
|
_ => &mut self.table_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fields(&self) -> Vec<&str> {
|
||||||
|
// These must match the order used in render_add_table
|
||||||
|
vec!["Table name", "Name", "Type"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_current_field(&mut self, index: usize) {
|
||||||
|
self.current_focus = match index {
|
||||||
|
0 => AddTableFocus::InputTableName,
|
||||||
|
1 => AddTableFocus::InputColumnName,
|
||||||
|
2 => AddTableFocus::InputColumnType,
|
||||||
|
_ => self.current_focus, // Stay on current focus if index is out of bounds
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_current_cursor_pos(&mut self, pos: usize) {
|
||||||
|
match self.current_focus {
|
||||||
|
AddTableFocus::InputTableName => self.table_name_cursor_pos = pos,
|
||||||
|
AddTableFocus::InputColumnName => self.column_name_cursor_pos = pos,
|
||||||
|
AddTableFocus::InputColumnType => self.column_type_cursor_pos = pos,
|
||||||
|
_ => {} // Do nothing if focus is not on an input field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_has_unsaved_changes(&mut self, changed: bool) {
|
||||||
|
self.has_unsaved_changes = changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Autocomplete Support (Not needed for this form yet) ---
|
||||||
|
fn get_suggestions(&self) -> Option<&[String]> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_selected_suggestion_index(&self) -> Option<usize> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -104,6 +104,8 @@ pub fn render_ui(
|
|||||||
theme,
|
theme,
|
||||||
app_state,
|
app_state,
|
||||||
&mut admin_state.add_table_state,
|
&mut admin_state.add_table_state,
|
||||||
|
login_state.current_field < 3,
|
||||||
|
highlight_state,
|
||||||
);
|
);
|
||||||
} else if app_state.ui.show_login {
|
} else if app_state.ui.show_login {
|
||||||
render_login(
|
render_login(
|
||||||
|
|||||||
Reference in New Issue
Block a user