286 lines
13 KiB
Rust
286 lines
13 KiB
Rust
// src/pages/admin_panel/add_table/event.rs
|
|
|
|
use anyhow::Result;
|
|
use crate::config::binds::config::Config;
|
|
use crate::movement::{move_focus, MovementAction};
|
|
use crate::pages::admin_panel::add_table::logic::{
|
|
handle_add_column_action, handle_delete_selected_columns, handle_save_table_action,
|
|
};
|
|
use crate::pages::admin_panel::add_table::nav::SaveTableResultSender;
|
|
use crate::pages::admin_panel::add_table::state::{AddTableFocus, AddTableFormState};
|
|
use crate::services::grpc_client::GrpcClient;
|
|
use crate::state::app::state::AppState;
|
|
use crate::modes::handlers::event::EventOutcome;
|
|
use canvas::{AppMode as CanvasMode, DataProvider};
|
|
use crossterm::event::KeyEvent;
|
|
|
|
/// Focus traversal order for AddTable (outside canvas)
|
|
const ADD_TABLE_FOCUS_ORDER: [AddTableFocus; 10] = [
|
|
AddTableFocus::InputTableName,
|
|
AddTableFocus::InputColumnName,
|
|
AddTableFocus::InputColumnType,
|
|
AddTableFocus::AddColumnButton,
|
|
AddTableFocus::ColumnsTable,
|
|
AddTableFocus::IndexesTable,
|
|
AddTableFocus::LinksTable,
|
|
AddTableFocus::SaveButton,
|
|
AddTableFocus::DeleteSelectedButton,
|
|
AddTableFocus::CancelButton,
|
|
];
|
|
|
|
/// Handles all AddTable page-specific events.
|
|
/// Return a non-empty Ok(message) only when the page actually consumed the key,
|
|
/// otherwise return Ok("") to let global handling proceed.
|
|
pub fn handle_add_table_event(
|
|
key_event: KeyEvent,
|
|
movement: Option<MovementAction>,
|
|
config: &Config,
|
|
app_state: &mut AppState,
|
|
page: &mut AddTableFormState,
|
|
mut grpc_client: GrpcClient,
|
|
save_result_sender: SaveTableResultSender,
|
|
) -> Result<EventOutcome> {
|
|
// 1) Inside canvas (FormEditor)
|
|
let inside_canvas_inputs = matches!(
|
|
page.current_focus(),
|
|
AddTableFocus::InputTableName
|
|
| AddTableFocus::InputColumnName
|
|
| AddTableFocus::InputColumnType
|
|
);
|
|
|
|
if inside_canvas_inputs {
|
|
// Disable global shortcuts while typing
|
|
app_state.ui.focus_outside_canvas = false;
|
|
|
|
// Only allow leaving the canvas with Down/Next when in ReadOnly mode
|
|
let in_edit_mode = page.editor.mode() == CanvasMode::Edit;
|
|
if !in_edit_mode {
|
|
if let Some(ma) = movement {
|
|
let last_idx = page.editor.data_provider().field_count().saturating_sub(1);
|
|
let at_last = page.editor.current_field() >= last_idx;
|
|
if at_last && matches!(ma, MovementAction::Down | MovementAction::Next) {
|
|
page.state.last_canvas_field = last_idx;
|
|
page.set_current_focus(AddTableFocus::AddColumnButton);
|
|
app_state.ui.focus_outside_canvas = true;
|
|
return Ok(EventOutcome::Ok("Moved to Add button".to_string()));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Let the FormEditor handle typing
|
|
match page.editor.handle_key_event(key_event) {
|
|
canvas::keymap::KeyEventOutcome::Consumed(Some(msg)) => {
|
|
page.sync_from_editor();
|
|
return Ok(EventOutcome::Ok(msg));
|
|
}
|
|
canvas::keymap::KeyEventOutcome::Consumed(None) => {
|
|
page.sync_from_editor();
|
|
return Ok(EventOutcome::Ok("Input updated".into()));
|
|
}
|
|
canvas::keymap::KeyEventOutcome::Pending => {
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
canvas::keymap::KeyEventOutcome::NotMatched => {
|
|
// fall through
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2) Outside canvas
|
|
if let Some(ma) = movement {
|
|
// Block outer moves when "inside" any table and handle locally
|
|
match page.current_focus() {
|
|
AddTableFocus::InsideColumnsTable => {
|
|
match ma {
|
|
MovementAction::Up => {
|
|
if let Some(i) = page.state.column_table_state.selected() {
|
|
let next = i.saturating_sub(1);
|
|
page.state.column_table_state.select(Some(next));
|
|
} else if !page.state.columns.is_empty() {
|
|
page.state.column_table_state.select(Some(0));
|
|
}
|
|
app_state.ui.focus_outside_canvas = true;
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
MovementAction::Down => {
|
|
if let Some(i) = page.state.column_table_state.selected() {
|
|
let last = page.state.columns.len().saturating_sub(1);
|
|
let next = if i < last { i + 1 } else { i };
|
|
page.state.column_table_state.select(Some(next));
|
|
} else if !page.state.columns.is_empty() {
|
|
page.state.column_table_state.select(Some(0));
|
|
}
|
|
app_state.ui.focus_outside_canvas = true;
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
MovementAction::Select => {
|
|
if let Some(i) = page.state.column_table_state.selected() {
|
|
if let Some(col) = page.state.columns.get_mut(i) {
|
|
col.selected = !col.selected;
|
|
page.state.has_unsaved_changes = true;
|
|
}
|
|
}
|
|
app_state.ui.focus_outside_canvas = true;
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
MovementAction::Esc => {
|
|
page.state.column_table_state.select(None);
|
|
page.set_current_focus(AddTableFocus::ColumnsTable);
|
|
app_state.ui.focus_outside_canvas = true;
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
MovementAction::Next | MovementAction::Previous => {
|
|
// Block outer movement while inside
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
AddTableFocus::InsideIndexesTable => {
|
|
match ma {
|
|
MovementAction::Up => {
|
|
if let Some(i) = page.state.index_table_state.selected() {
|
|
let next = i.saturating_sub(1);
|
|
page.state.index_table_state.select(Some(next));
|
|
} else if !page.state.indexes.is_empty() {
|
|
page.state.index_table_state.select(Some(0));
|
|
}
|
|
app_state.ui.focus_outside_canvas = true;
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
MovementAction::Down => {
|
|
if let Some(i) = page.state.index_table_state.selected() {
|
|
let last = page.state.indexes.len().saturating_sub(1);
|
|
let next = if i < last { i + 1 } else { i };
|
|
page.state.index_table_state.select(Some(next));
|
|
} else if !page.state.indexes.is_empty() {
|
|
page.state.index_table_state.select(Some(0));
|
|
}
|
|
app_state.ui.focus_outside_canvas = true;
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
MovementAction::Select => {
|
|
if let Some(i) = page.state.index_table_state.selected() {
|
|
if let Some(ix) = page.state.indexes.get_mut(i) {
|
|
ix.selected = !ix.selected;
|
|
page.state.has_unsaved_changes = true;
|
|
}
|
|
}
|
|
app_state.ui.focus_outside_canvas = true;
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
MovementAction::Esc => {
|
|
page.state.index_table_state.select(None);
|
|
page.set_current_focus(AddTableFocus::IndexesTable);
|
|
app_state.ui.focus_outside_canvas = true;
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
MovementAction::Next | MovementAction::Previous => {
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
AddTableFocus::InsideLinksTable => {
|
|
match ma {
|
|
MovementAction::Up => {
|
|
if let Some(i) = page.state.link_table_state.selected() {
|
|
let next = i.saturating_sub(1);
|
|
page.state.link_table_state.select(Some(next));
|
|
} else if !page.state.links.is_empty() {
|
|
page.state.link_table_state.select(Some(0));
|
|
}
|
|
app_state.ui.focus_outside_canvas = true;
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
MovementAction::Down => {
|
|
if let Some(i) = page.state.link_table_state.selected() {
|
|
let last = page.state.links.len().saturating_sub(1);
|
|
let next = if i < last { i + 1 } else { i };
|
|
page.state.link_table_state.select(Some(next));
|
|
} else if !page.state.links.is_empty() {
|
|
page.state.link_table_state.select(Some(0));
|
|
}
|
|
app_state.ui.focus_outside_canvas = true;
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
MovementAction::Select => {
|
|
if let Some(i) = page.state.link_table_state.selected() {
|
|
if let Some(link) = page.state.links.get_mut(i) {
|
|
link.selected = !link.selected;
|
|
page.state.has_unsaved_changes = true;
|
|
}
|
|
}
|
|
app_state.ui.focus_outside_canvas = true;
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
MovementAction::Esc => {
|
|
page.state.link_table_state.select(None);
|
|
page.set_current_focus(AddTableFocus::LinksTable);
|
|
app_state.ui.focus_outside_canvas = true;
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
MovementAction::Next | MovementAction::Previous => {
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
let mut current = page.current_focus();
|
|
if move_focus(&ADD_TABLE_FOCUS_ORDER, &mut current, ma) {
|
|
page.set_current_focus(current);
|
|
app_state.ui.focus_outside_canvas = !matches!(
|
|
page.current_focus(),
|
|
AddTableFocus::InputTableName
|
|
| AddTableFocus::InputColumnName
|
|
| AddTableFocus::InputColumnType
|
|
);
|
|
return Ok(EventOutcome::Ok(String::new()));
|
|
}
|
|
|
|
// 3) Rich actions
|
|
match ma {
|
|
MovementAction::Select => match page.current_focus() {
|
|
AddTableFocus::AddColumnButton => {
|
|
if let Some(focus_after_add) =
|
|
handle_add_column_action(&mut page.state, &mut String::new())
|
|
{
|
|
page.set_current_focus(focus_after_add);
|
|
return Ok(EventOutcome::Ok("Column added".into()));
|
|
}
|
|
}
|
|
AddTableFocus::SaveButton => {
|
|
if page.state.table_name.is_empty() {
|
|
return Ok(EventOutcome::Ok("Cannot save: Table name is empty".into()));
|
|
}
|
|
if page.state.columns.is_empty() {
|
|
return Ok(EventOutcome::Ok("Cannot save: No columns defined".into()));
|
|
}
|
|
app_state.show_loading_dialog("Saving", "Please wait...");
|
|
let state_clone = page.state.clone();
|
|
let sender_clone = save_result_sender.clone();
|
|
tokio::spawn(async move {
|
|
let result = handle_save_table_action(&mut grpc_client, &state_clone).await;
|
|
let _ = sender_clone.send(result).await;
|
|
});
|
|
return Ok(EventOutcome::Ok("Saving table...".into()));
|
|
}
|
|
AddTableFocus::DeleteSelectedButton => {
|
|
let msg = handle_delete_selected_columns(&mut page.state);
|
|
return Ok(EventOutcome::Ok(msg));
|
|
}
|
|
AddTableFocus::CancelButton => {
|
|
return Ok(EventOutcome::Ok("Cancelled Add Table".to_string()));
|
|
}
|
|
_ => {}
|
|
},
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
Ok(EventOutcome::Ok(String::new()))
|
|
}
|