307 lines
10 KiB
Rust
307 lines
10 KiB
Rust
// src/state/pages/add_table.rs
|
|
use canvas::canvas::{CanvasState, ActionContext, CanvasAction, AppMode};
|
|
use ratatui::widgets::TableState;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct ColumnDefinition {
|
|
pub name: String,
|
|
pub data_type: String,
|
|
pub selected: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct IndexDefinition {
|
|
pub name: String,
|
|
pub selected: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct LinkDefinition {
|
|
pub linked_table_name: String,
|
|
pub is_required: bool,
|
|
pub selected: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
pub enum AddTableFocus {
|
|
#[default]
|
|
InputTableName, // Field 0 for CanvasState
|
|
InputColumnName, // Field 1 for CanvasState
|
|
InputColumnType, // Field 2 for CanvasState
|
|
AddColumnButton,
|
|
// Result Tables
|
|
ColumnsTable,
|
|
IndexesTable,
|
|
LinksTable,
|
|
// Inside Tables (Scrolling Focus)
|
|
InsideColumnsTable,
|
|
InsideIndexesTable,
|
|
InsideLinksTable,
|
|
// Buttons
|
|
SaveButton,
|
|
DeleteSelectedButton,
|
|
CancelButton,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct AddTableState {
|
|
pub profile_name: String,
|
|
pub table_name: String,
|
|
pub table_name_input: String,
|
|
pub column_name_input: String,
|
|
pub column_type_input: String,
|
|
pub columns: Vec<ColumnDefinition>,
|
|
pub indexes: Vec<IndexDefinition>,
|
|
pub links: Vec<LinkDefinition>,
|
|
pub current_focus: AddTableFocus,
|
|
pub last_canvas_field: usize,
|
|
pub column_table_state: TableState,
|
|
pub index_table_state: TableState,
|
|
pub link_table_state: TableState,
|
|
pub table_name_cursor_pos: usize,
|
|
pub column_name_cursor_pos: usize,
|
|
pub column_type_cursor_pos: usize,
|
|
pub has_unsaved_changes: bool,
|
|
pub app_mode: AppMode,
|
|
}
|
|
|
|
impl Default for AddTableState {
|
|
fn default() -> Self {
|
|
AddTableState {
|
|
profile_name: "default".to_string(),
|
|
table_name: String::new(),
|
|
table_name_input: String::new(),
|
|
column_name_input: String::new(),
|
|
column_type_input: String::new(),
|
|
columns: Vec::new(),
|
|
indexes: Vec::new(),
|
|
links: Vec::new(),
|
|
current_focus: AddTableFocus::InputTableName,
|
|
last_canvas_field: 2,
|
|
column_table_state: TableState::default(),
|
|
index_table_state: TableState::default(),
|
|
link_table_state: TableState::default(),
|
|
table_name_cursor_pos: 0,
|
|
column_name_cursor_pos: 0,
|
|
column_type_cursor_pos: 0,
|
|
has_unsaved_changes: false,
|
|
app_mode: AppMode::Edit,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AddTableState {
|
|
pub const INPUT_FIELD_COUNT: usize = 3;
|
|
|
|
/// Helper method to add a column from current inputs
|
|
pub fn add_column_from_inputs(&mut self) -> Option<String> {
|
|
if self.column_name_input.trim().is_empty() || self.column_type_input.trim().is_empty() {
|
|
return Some("Both column name and type are required".to_string());
|
|
}
|
|
|
|
// Check for duplicate column names
|
|
if self.columns.iter().any(|col| col.name == self.column_name_input.trim()) {
|
|
return Some("Column name already exists".to_string());
|
|
}
|
|
|
|
// Add the column
|
|
self.columns.push(ColumnDefinition {
|
|
name: self.column_name_input.trim().to_string(),
|
|
data_type: self.column_type_input.trim().to_string(),
|
|
selected: false,
|
|
});
|
|
|
|
// Clear inputs and reset focus to column name for next entry
|
|
self.column_name_input.clear();
|
|
self.column_type_input.clear();
|
|
self.column_name_cursor_pos = 0;
|
|
self.column_type_cursor_pos = 0;
|
|
self.current_focus = AddTableFocus::InputColumnName;
|
|
self.last_canvas_field = 1;
|
|
self.has_unsaved_changes = true;
|
|
|
|
Some(format!("Column '{}' added successfully", self.columns.last().unwrap().name))
|
|
}
|
|
|
|
/// Helper method to delete selected items
|
|
pub fn delete_selected_items(&mut self) -> Option<String> {
|
|
let mut deleted_items = Vec::new();
|
|
|
|
// Remove selected columns
|
|
let initial_column_count = self.columns.len();
|
|
self.columns.retain(|col| {
|
|
if col.selected {
|
|
deleted_items.push(format!("column '{}'", col.name));
|
|
false
|
|
} else {
|
|
true
|
|
}
|
|
});
|
|
|
|
// Remove selected indexes
|
|
let initial_index_count = self.indexes.len();
|
|
self.indexes.retain(|idx| {
|
|
if idx.selected {
|
|
deleted_items.push(format!("index '{}'", idx.name));
|
|
false
|
|
} else {
|
|
true
|
|
}
|
|
});
|
|
|
|
// Remove selected links
|
|
let initial_link_count = self.links.len();
|
|
self.links.retain(|link| {
|
|
if link.selected {
|
|
deleted_items.push(format!("link to '{}'", link.linked_table_name));
|
|
false
|
|
} else {
|
|
true
|
|
}
|
|
});
|
|
|
|
if deleted_items.is_empty() {
|
|
Some("No items selected for deletion".to_string())
|
|
} else {
|
|
self.has_unsaved_changes = true;
|
|
Some(format!("Deleted: {}", deleted_items.join(", ")))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Implement external library's CanvasState for AddTableState
|
|
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, return the last canvas field used
|
|
_ => self.last_canvas_field,
|
|
}
|
|
}
|
|
|
|
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 set_current_field(&mut self, index: usize) {
|
|
// Update both current focus and last canvas field
|
|
self.current_focus = match index {
|
|
0 => {
|
|
self.last_canvas_field = 0;
|
|
AddTableFocus::InputTableName
|
|
},
|
|
1 => {
|
|
self.last_canvas_field = 1;
|
|
AddTableFocus::InputColumnName
|
|
},
|
|
2 => {
|
|
self.last_canvas_field = 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 get_current_input(&self) -> &str {
|
|
match self.current_focus {
|
|
AddTableFocus::InputTableName => &self.table_name_input,
|
|
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_input,
|
|
AddTableFocus::InputColumnName => &mut self.column_name_input,
|
|
AddTableFocus::InputColumnType => &mut self.column_type_input,
|
|
_ => &mut self.table_name_input, // Fallback
|
|
}
|
|
}
|
|
|
|
fn inputs(&self) -> Vec<&String> {
|
|
vec![&self.table_name_input, &self.column_name_input, &self.column_type_input]
|
|
}
|
|
|
|
fn fields(&self) -> Vec<&str> {
|
|
// These must match the order used in render_add_table
|
|
vec!["Table name", "Name", "Type"]
|
|
}
|
|
|
|
fn has_unsaved_changes(&self) -> bool {
|
|
self.has_unsaved_changes
|
|
}
|
|
|
|
fn set_has_unsaved_changes(&mut self, changed: bool) {
|
|
self.has_unsaved_changes = changed;
|
|
}
|
|
|
|
fn handle_feature_action(&mut self, action: &CanvasAction, _context: &ActionContext) -> Option<String> {
|
|
match action {
|
|
// Handle adding column when user presses Enter on the Add button or uses specific action
|
|
CanvasAction::Custom(action_str) if action_str == "add_column" => {
|
|
self.add_column_from_inputs()
|
|
}
|
|
|
|
// Handle table saving
|
|
CanvasAction::Custom(action_str) if action_str == "save_table" => {
|
|
if self.table_name_input.trim().is_empty() {
|
|
Some("Table name is required".to_string())
|
|
} else if self.columns.is_empty() {
|
|
Some("At least one column is required".to_string())
|
|
} else {
|
|
Some(format!("Saving table: {}", self.table_name_input))
|
|
}
|
|
}
|
|
|
|
// Handle deleting selected items
|
|
CanvasAction::Custom(action_str) if action_str == "delete_selected" => {
|
|
self.delete_selected_items()
|
|
}
|
|
|
|
// Handle canceling (clear form)
|
|
CanvasAction::Custom(action_str) if action_str == "cancel" => {
|
|
// Reset to defaults but keep profile_name
|
|
let profile = self.profile_name.clone();
|
|
*self = Self::default();
|
|
self.profile_name = profile;
|
|
Some("Form cleared".to_string())
|
|
}
|
|
|
|
// Custom validation when moving between fields
|
|
CanvasAction::NextField => {
|
|
// When leaving table name field, update the table_name for display
|
|
if self.current_field() == 0 && !self.table_name_input.trim().is_empty() {
|
|
self.table_name = self.table_name_input.trim().to_string();
|
|
}
|
|
None // Let canvas library handle the normal field movement
|
|
}
|
|
|
|
// Let canvas library handle everything else
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn current_mode(&self) -> AppMode {
|
|
self.app_mode
|
|
}
|
|
}
|