moved add_table to be feature based

This commit is contained in:
filipriec
2025-08-30 14:25:33 +02:00
parent 42db496ad7
commit 10f4b9d8e2
17 changed files with 30 additions and 25 deletions

View 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 doesnt 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)
}
}