moved add_table to be feature based
This commit is contained in:
383
client/src/pages/admin_panel/add_table/state.rs
Normal file
383
client/src/pages/admin_panel/add_table/state.rs
Normal file
@@ -0,0 +1,383 @@
|
||||
// src/pages/admin_panel/add_table/state.rs
|
||||
|
||||
use canvas::{DataProvider, AppMode};
|
||||
use ratatui::widgets::TableState;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::movement::{move_focus, MovementAction};
|
||||
|
||||
#[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: canvas::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: canvas::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(", ")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DataProvider for AddTableState {
|
||||
fn field_count(&self) -> usize {
|
||||
3 // Table name, Column name, Column type
|
||||
}
|
||||
|
||||
fn field_name(&self, index: usize) -> &str {
|
||||
match index {
|
||||
0 => "Table name",
|
||||
1 => "Name",
|
||||
2 => "Type",
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
fn field_value(&self, index: usize) -> &str {
|
||||
match index {
|
||||
0 => &self.table_name_input,
|
||||
1 => &self.column_name_input,
|
||||
2 => &self.column_type_input,
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
fn set_field_value(&mut self, index: usize, value: String) {
|
||||
match index {
|
||||
0 => self.table_name_input = value,
|
||||
1 => self.column_name_input = value,
|
||||
2 => self.column_type_input = value,
|
||||
_ => {}
|
||||
}
|
||||
self.has_unsaved_changes = true;
|
||||
}
|
||||
|
||||
fn supports_suggestions(&self, _field_index: usize) -> bool {
|
||||
false // AddTableState doesn’t use suggestions
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl AddTableState {
|
||||
pub fn handle_movement(&mut self, action: MovementAction) -> bool {
|
||||
use AddTableFocus::*;
|
||||
|
||||
// Linear outer focus order
|
||||
const ORDER: [AddTableFocus; 10] = [
|
||||
InputTableName,
|
||||
InputColumnName,
|
||||
InputColumnType,
|
||||
AddColumnButton,
|
||||
ColumnsTable,
|
||||
IndexesTable,
|
||||
LinksTable,
|
||||
SaveButton,
|
||||
DeleteSelectedButton,
|
||||
CancelButton,
|
||||
];
|
||||
|
||||
// Enter "inside" on Select from outer panes
|
||||
match (self.current_focus, action) {
|
||||
(ColumnsTable, MovementAction::Select) => {
|
||||
if !self.columns.is_empty() && self.column_table_state.selected().is_none() {
|
||||
self.column_table_state.select(Some(0));
|
||||
}
|
||||
self.current_focus = InsideColumnsTable;
|
||||
return true;
|
||||
}
|
||||
(IndexesTable, MovementAction::Select) => {
|
||||
if !self.indexes.is_empty() && self.index_table_state.selected().is_none() {
|
||||
self.index_table_state.select(Some(0));
|
||||
}
|
||||
self.current_focus = InsideIndexesTable;
|
||||
return true;
|
||||
}
|
||||
(LinksTable, MovementAction::Select) => {
|
||||
if !self.links.is_empty() && self.link_table_state.selected().is_none() {
|
||||
self.link_table_state.select(Some(0));
|
||||
}
|
||||
self.current_focus = InsideLinksTable;
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Handle "inside" states: Up/Down/Select/Esc; block outer movement keys
|
||||
match self.current_focus {
|
||||
InsideColumnsTable => {
|
||||
match action {
|
||||
MovementAction::Up => {
|
||||
if let Some(i) = self.column_table_state.selected() {
|
||||
let next = i.saturating_sub(1);
|
||||
self.column_table_state.select(Some(next));
|
||||
} else if !self.columns.is_empty() {
|
||||
self.column_table_state.select(Some(0));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
MovementAction::Down => {
|
||||
if let Some(i) = self.column_table_state.selected() {
|
||||
let last = self.columns.len().saturating_sub(1);
|
||||
let next = if i < last { i + 1 } else { i };
|
||||
self.column_table_state.select(Some(next));
|
||||
} else if !self.columns.is_empty() {
|
||||
self.column_table_state.select(Some(0));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
MovementAction::Select => {
|
||||
if let Some(i) = self.column_table_state.selected() {
|
||||
if let Some(col) = self.columns.get_mut(i) {
|
||||
col.selected = !col.selected;
|
||||
self.has_unsaved_changes = true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
MovementAction::Esc => {
|
||||
self.column_table_state.select(None);
|
||||
self.current_focus = ColumnsTable;
|
||||
return true;
|
||||
}
|
||||
MovementAction::Next | MovementAction::Previous => return true, // block outer moves
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
InsideIndexesTable => {
|
||||
match action {
|
||||
MovementAction::Up => {
|
||||
if let Some(i) = self.index_table_state.selected() {
|
||||
let next = i.saturating_sub(1);
|
||||
self.index_table_state.select(Some(next));
|
||||
} else if !self.indexes.is_empty() {
|
||||
self.index_table_state.select(Some(0));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
MovementAction::Down => {
|
||||
if let Some(i) = self.index_table_state.selected() {
|
||||
let last = self.indexes.len().saturating_sub(1);
|
||||
let next = if i < last { i + 1 } else { i };
|
||||
self.index_table_state.select(Some(next));
|
||||
} else if !self.indexes.is_empty() {
|
||||
self.index_table_state.select(Some(0));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
MovementAction::Select => {
|
||||
if let Some(i) = self.index_table_state.selected() {
|
||||
if let Some(ix) = self.indexes.get_mut(i) {
|
||||
ix.selected = !ix.selected;
|
||||
self.has_unsaved_changes = true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
MovementAction::Esc => {
|
||||
self.index_table_state.select(None);
|
||||
self.current_focus = IndexesTable;
|
||||
return true;
|
||||
}
|
||||
MovementAction::Next | MovementAction::Previous => return true, // block outer moves
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
InsideLinksTable => {
|
||||
match action {
|
||||
MovementAction::Up => {
|
||||
if let Some(i) = self.link_table_state.selected() {
|
||||
let next = i.saturating_sub(1);
|
||||
self.link_table_state.select(Some(next));
|
||||
} else if !self.links.is_empty() {
|
||||
self.link_table_state.select(Some(0));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
MovementAction::Down => {
|
||||
if let Some(i) = self.link_table_state.selected() {
|
||||
let last = self.links.len().saturating_sub(1);
|
||||
let next = if i < last { i + 1 } else { i };
|
||||
self.link_table_state.select(Some(next));
|
||||
} else if !self.links.is_empty() {
|
||||
self.link_table_state.select(Some(0));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
MovementAction::Select => {
|
||||
if let Some(i) = self.link_table_state.selected() {
|
||||
if let Some(link) = self.links.get_mut(i) {
|
||||
link.selected = !link.selected;
|
||||
self.has_unsaved_changes = true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
MovementAction::Esc => {
|
||||
self.link_table_state.select(None);
|
||||
self.current_focus = LinksTable;
|
||||
return true;
|
||||
}
|
||||
MovementAction::Next | MovementAction::Previous => return true, // block outer moves
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Default: outer navigation via helper
|
||||
move_focus(&ORDER, &mut self.current_focus, action)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user