323 lines
9.9 KiB
Rust
323 lines
9.9 KiB
Rust
// src/pages/admin_panel/add_table/state.rs
|
||
|
||
use canvas::{DataProvider, AppMode};
|
||
use canvas::FormEditor;
|
||
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: 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> {
|
||
let table_name_in = self.table_name_input.trim().to_string();
|
||
let column_name_in = self.column_name_input.trim().to_string();
|
||
let column_type_in = self.column_type_input.trim().to_string();
|
||
|
||
// Case: "only table name" provided → set it and stay on TableName
|
||
if !table_name_in.is_empty() && column_name_in.is_empty() && column_type_in.is_empty() {
|
||
self.table_name = table_name_in;
|
||
self.table_name_input.clear();
|
||
self.table_name_cursor_pos = 0;
|
||
self.current_focus = AddTableFocus::InputTableName;
|
||
self.has_unsaved_changes = true;
|
||
return Some(format!("Table name set to '{}'.", self.table_name));
|
||
}
|
||
|
||
// Column validation
|
||
if column_name_in.is_empty() || column_type_in.is_empty() {
|
||
return Some("Both column name and type are required".to_string());
|
||
}
|
||
if self.columns.iter().any(|col| col.name == column_name_in) {
|
||
return Some("Column name already exists".to_string());
|
||
}
|
||
|
||
// If table_name input present while adding first column, apply it too
|
||
if !table_name_in.is_empty() {
|
||
self.table_name = table_name_in;
|
||
self.table_name_input.clear();
|
||
self.table_name_cursor_pos = 0;
|
||
}
|
||
|
||
// Add the column
|
||
self.columns.push(ColumnDefinition {
|
||
name: column_name_in.clone(),
|
||
data_type: column_type_in.clone(),
|
||
selected: false,
|
||
});
|
||
// Add a corresponding (unselected) index with the same name
|
||
self.indexes.push(IndexDefinition {
|
||
name: column_name_in.clone(),
|
||
selected: false,
|
||
});
|
||
|
||
// Clear column inputs and set focus 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", column_name_in))
|
||
}
|
||
|
||
/// Helper method to delete selected items
|
||
pub fn delete_selected_items(&mut self) -> Option<String> {
|
||
let mut deleted_items: Vec<String> = Vec::new();
|
||
|
||
// Remove selected columns
|
||
let selected_col_names: std::collections::HashSet<String> = self
|
||
.columns
|
||
.iter()
|
||
.filter(|c| c.selected)
|
||
.map(|c| c.name.clone())
|
||
.collect();
|
||
if !selected_col_names.is_empty() {
|
||
self.columns.retain(|col| {
|
||
if selected_col_names.contains(&col.name) {
|
||
deleted_items.push(format!("column '{}'", col.name));
|
||
false
|
||
} else {
|
||
true
|
||
}
|
||
});
|
||
// Also purge indexes for deleted columns
|
||
self.indexes
|
||
.retain(|idx| !selected_col_names.contains(&idx.name));
|
||
}
|
||
|
||
// 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;
|
||
self.column_table_state.select(None);
|
||
self.index_table_state.select(None);
|
||
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
|
||
}
|
||
}
|
||
|
||
pub struct AddTableFormState {
|
||
pub state: AddTableState,
|
||
pub editor: FormEditor<AddTableState>,
|
||
pub focus_outside_canvas: bool,
|
||
}
|
||
|
||
impl std::fmt::Debug for AddTableFormState {
|
||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
f.debug_struct("AddTableFormState")
|
||
.field("state", &self.state)
|
||
.field("focus_outside_canvas", &self.focus_outside_canvas)
|
||
.finish()
|
||
}
|
||
}
|
||
|
||
impl AddTableFormState {
|
||
pub fn new(profile_name: String) -> Self {
|
||
let mut state = AddTableState::default();
|
||
state.profile_name = profile_name;
|
||
let editor = FormEditor::new(state.clone());
|
||
Self {
|
||
state,
|
||
editor,
|
||
focus_outside_canvas: false,
|
||
}
|
||
}
|
||
|
||
pub fn from_state(state: AddTableState) -> Self {
|
||
let editor = FormEditor::new(state.clone());
|
||
Self {
|
||
state,
|
||
editor,
|
||
focus_outside_canvas: false,
|
||
}
|
||
}
|
||
|
||
/// Sync state from editor’s snapshot
|
||
pub fn sync_from_editor(&mut self) {
|
||
self.state = self.editor.data_provider().clone();
|
||
}
|
||
|
||
// === Delegates to AddTableState fields ===
|
||
pub fn current_focus(&self) -> AddTableFocus {
|
||
self.state.current_focus
|
||
}
|
||
pub fn set_current_focus(&mut self, focus: AddTableFocus) {
|
||
self.state.current_focus = focus;
|
||
}
|
||
pub fn profile_name(&self) -> &str {
|
||
&self.state.profile_name
|
||
}
|
||
pub fn table_name(&self) -> &str {
|
||
&self.state.table_name
|
||
}
|
||
pub fn columns(&self) -> &Vec<ColumnDefinition> {
|
||
&self.state.columns
|
||
}
|
||
pub fn indexes(&self) -> &Vec<IndexDefinition> {
|
||
&self.state.indexes
|
||
}
|
||
pub fn links(&self) -> &Vec<LinkDefinition> {
|
||
&self.state.links
|
||
}
|
||
pub fn column_table_state(&mut self) -> &mut TableState {
|
||
&mut self.state.column_table_state
|
||
}
|
||
pub fn index_table_state(&mut self) -> &mut TableState {
|
||
&mut self.state.index_table_state
|
||
}
|
||
pub fn link_table_state(&mut self) -> &mut TableState {
|
||
&mut self.state.link_table_state
|
||
}
|
||
}
|