Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd7c97ca91 | ||
|
|
81235c67dc | ||
|
|
65e8e03224 | ||
|
|
85eb3adec7 |
@@ -14,7 +14,6 @@ use ratatui::{
|
||||
use crate::components::handlers::canvas::render_canvas;
|
||||
use crate::components::common::dialog;
|
||||
use crate::config::binds::config::EditorKeybindingMode;
|
||||
use crate::components::common::text_editor::TextEditor;
|
||||
|
||||
pub fn render_add_logic(
|
||||
f: &mut Frame,
|
||||
@@ -22,7 +21,7 @@ pub fn render_add_logic(
|
||||
theme: &Theme,
|
||||
app_state: &AppState,
|
||||
add_logic_state: &mut AddLogicState,
|
||||
is_edit_mode: bool,
|
||||
is_edit_mode: bool, // Used for border/title hints in InsideScriptContent
|
||||
highlight_state: &HighlightState,
|
||||
) {
|
||||
let main_block = Block::default()
|
||||
@@ -35,27 +34,30 @@ pub fn render_add_logic(
|
||||
let inner_area = main_block.inner(area);
|
||||
f.render_widget(main_block, area);
|
||||
|
||||
if add_logic_state.current_focus == AddLogicFocus::InputScriptContent {
|
||||
// Handle full-screen script editing
|
||||
if add_logic_state.current_focus == AddLogicFocus::InsideScriptContent {
|
||||
let mut editor_ref = add_logic_state.script_content_editor.borrow_mut();
|
||||
let border_style_color = if is_edit_mode { theme.highlight } else { theme.secondary };
|
||||
let border_style = Style::default().fg(border_style_color);
|
||||
|
||||
editor_ref.set_cursor_line_style(Style::default());
|
||||
// Explicitly set to tui-textarea's default "active" editing cursor style
|
||||
editor_ref.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
||||
|
||||
let script_title_hint = match add_logic_state.editor_keybinding_mode {
|
||||
EditorKeybindingMode::Vim => {
|
||||
let vim_mode_status = TextEditor::get_vim_mode_status(&add_logic_state.vim_state);
|
||||
if is_edit_mode {
|
||||
format!("Script (VIM {}) - Esc for Normal. Tab navigates from Normal.", vim_mode_status)
|
||||
let vim_mode_status = crate::components::common::text_editor::TextEditor::get_vim_mode_status(&add_logic_state.vim_state);
|
||||
if is_edit_mode { // is_edit_mode here refers to Vim's Insert mode
|
||||
format!("Script {}", vim_mode_status)
|
||||
} else {
|
||||
format!("Script (VIM {}) - 'i'/'a'/'o' for Insert. Tab to navigate.", vim_mode_status)
|
||||
format!("Script {}", vim_mode_status)
|
||||
}
|
||||
}
|
||||
EditorKeybindingMode::Emacs | EditorKeybindingMode::Default => {
|
||||
if is_edit_mode {
|
||||
"Script (Editing - Esc to exit edit. Tab navigates after exit.)".to_string()
|
||||
"Script (Editing)".to_string()
|
||||
} else {
|
||||
"Script (Press Enter or Ctrl+E to edit. Tab to navigate.)".to_string()
|
||||
"Script".to_string()
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -68,19 +70,18 @@ pub fn render_add_logic(
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(border_style),
|
||||
);
|
||||
// Remove .widget() call - just pass the reference directly
|
||||
f.render_widget(&*editor_ref, inner_area);
|
||||
return;
|
||||
}
|
||||
|
||||
// ... rest of the layout code ...
|
||||
// Regular layout with preview
|
||||
let main_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(9),
|
||||
Constraint::Min(5),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3), // Top info
|
||||
Constraint::Length(9), // Canvas
|
||||
Constraint::Min(5), // Script preview
|
||||
Constraint::Length(3), // Buttons
|
||||
])
|
||||
.split(inner_area);
|
||||
|
||||
@@ -89,6 +90,7 @@ pub fn render_add_logic(
|
||||
let script_content_area = main_chunks[2];
|
||||
let buttons_area = main_chunks[3];
|
||||
|
||||
// Top info
|
||||
let profile_text = Paragraph::new(vec![
|
||||
Line::from(Span::styled(
|
||||
format!("Profile: {}", add_logic_state.profile_name),
|
||||
@@ -114,6 +116,7 @@ pub fn render_add_logic(
|
||||
);
|
||||
f.render_widget(profile_text, top_info_area);
|
||||
|
||||
// Canvas
|
||||
let focus_on_canvas_inputs = matches!(
|
||||
add_logic_state.current_focus,
|
||||
AddLogicFocus::InputLogicName
|
||||
@@ -128,36 +131,57 @@ pub fn render_add_logic(
|
||||
&add_logic_state.current_field(),
|
||||
&add_logic_state.inputs(),
|
||||
theme,
|
||||
is_edit_mode && focus_on_canvas_inputs,
|
||||
is_edit_mode && focus_on_canvas_inputs, // is_edit_mode for canvas fields
|
||||
highlight_state,
|
||||
);
|
||||
|
||||
// Script content preview
|
||||
{
|
||||
let mut editor_ref = add_logic_state.script_content_editor.borrow_mut();
|
||||
editor_ref.set_cursor_line_style(Style::default());
|
||||
|
||||
let border_style_color = if add_logic_state.current_focus == AddLogicFocus::InputScriptContent {
|
||||
theme.highlight
|
||||
let is_script_preview_focused = add_logic_state.current_focus == AddLogicFocus::ScriptContentPreview;
|
||||
|
||||
if is_script_preview_focused {
|
||||
// When script PREVIEW is focused, use tui-textarea's default "active" cursor (block-like).
|
||||
editor_ref.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
||||
} else {
|
||||
// When script PREVIEW is NOT focused, use an underscore cursor.
|
||||
let underscore_cursor_style = Style::default()
|
||||
.add_modifier(Modifier::UNDERLINED)
|
||||
.fg(theme.secondary);
|
||||
editor_ref.set_cursor_style(underscore_cursor_style);
|
||||
}
|
||||
|
||||
let border_style_color = if is_script_preview_focused {
|
||||
theme.highlight // Green highlight when focused and ready to select
|
||||
} else {
|
||||
theme.secondary
|
||||
};
|
||||
|
||||
let title_hint = match add_logic_state.editor_keybinding_mode {
|
||||
EditorKeybindingMode::Vim => "Script Preview (VIM - Focus with Tab, then 'i'/'a'/'o' to edit)",
|
||||
_ => "Script Preview (Focus with Tab, then Enter/Ctrl+E to edit)",
|
||||
let title_text = if is_script_preview_focused {
|
||||
"Script Preview"
|
||||
} else {
|
||||
"Script Preview"
|
||||
};
|
||||
|
||||
let title_style = if is_script_preview_focused {
|
||||
Style::default().fg(theme.highlight).add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(theme.fg)
|
||||
};
|
||||
|
||||
editor_ref.set_block(
|
||||
Block::default()
|
||||
.title(title_hint)
|
||||
.title(Span::styled(title_text, title_style))
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(border_style_color)),
|
||||
);
|
||||
// Remove .widget() call here too
|
||||
f.render_widget(&*editor_ref, script_content_area);
|
||||
}
|
||||
|
||||
// Buttons
|
||||
let get_button_style = |button_focus: AddLogicFocus, current_focus| {
|
||||
let is_focused = current_focus == button_focus;
|
||||
let base_style = Style::default().fg(if is_focused {
|
||||
@@ -222,6 +246,7 @@ pub fn render_add_logic(
|
||||
);
|
||||
f.render_widget(cancel_button, button_chunks[1]);
|
||||
|
||||
// Dialog
|
||||
if app_state.ui.dialog.dialog_show {
|
||||
dialog::render_dialog(
|
||||
f,
|
||||
|
||||
@@ -10,9 +10,7 @@ use crossterm::event::{KeyEvent, KeyCode, KeyModifiers};
|
||||
use crate::services::GrpcClient;
|
||||
use tokio::sync::mpsc;
|
||||
use anyhow::Result;
|
||||
use common::proto::multieko2::table_script::PostTableScriptRequest;
|
||||
use crate::components::common::text_editor::TextEditor;
|
||||
use tui_textarea::Input as TextAreaInput;
|
||||
|
||||
pub type SaveLogicResultSender = mpsc::Sender<Result<String>>;
|
||||
|
||||
@@ -27,236 +25,214 @@ pub fn handle_add_logic_navigation(
|
||||
save_logic_sender: SaveLogicResultSender,
|
||||
command_message: &mut String,
|
||||
) -> bool {
|
||||
let mut handled = false;
|
||||
let general_action = config.get_general_action(key_event.code, key_event.modifiers);
|
||||
|
||||
if add_logic_state.current_focus == AddLogicFocus::InputScriptContent {
|
||||
// === FULLSCREEN SCRIPT EDITING - COMPLETE ISOLATION ===
|
||||
if add_logic_state.current_focus == AddLogicFocus::InsideScriptContent {
|
||||
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
|
||||
|
||||
match add_logic_state.editor_keybinding_mode {
|
||||
EditorKeybindingMode::Vim => {
|
||||
if *is_edit_mode { // App considers textarea to be in "typing" (Insert) mode
|
||||
let changed = TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
key_event,
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state,
|
||||
);
|
||||
if changed { add_logic_state.has_unsaved_changes = true; }
|
||||
|
||||
// Check if we've transitioned to Normal mode
|
||||
if key_event.code == KeyCode::Esc && TextEditor::is_vim_normal_mode(&add_logic_state.vim_state) {
|
||||
*is_edit_mode = false;
|
||||
*command_message = "VIM: Normal Mode. Tab to navigate.".to_string();
|
||||
}
|
||||
handled = true;
|
||||
} else { // App considers textarea to be in "navigation" (Normal) mode
|
||||
match key_event.code {
|
||||
// Keys to enter Vim Insert mode
|
||||
KeyCode::Char('i') | KeyCode::Char('a') | KeyCode::Char('o') |
|
||||
KeyCode::Char('I') | KeyCode::Char('A') | KeyCode::Char('O') => {
|
||||
*is_edit_mode = true;
|
||||
TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
key_event,
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state
|
||||
);
|
||||
*command_message = "VIM: Insert Mode.".to_string();
|
||||
handled = true;
|
||||
}
|
||||
_ => {
|
||||
if general_action.is_none() {
|
||||
let changed = TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
key_event,
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state,
|
||||
);
|
||||
if changed { add_logic_state.has_unsaved_changes = true; }
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorKeybindingMode::Emacs | EditorKeybindingMode::Default => {
|
||||
if *is_edit_mode {
|
||||
// Handle ONLY Escape to exit fullscreen mode
|
||||
if key_event.code == KeyCode::Esc && key_event.modifiers == KeyModifiers::NONE {
|
||||
*is_edit_mode = false;
|
||||
*command_message = "Exited script edit. Tab to navigate.".to_string();
|
||||
handled = true;
|
||||
} else if general_action.is_some() && (general_action.unwrap() == "next_field" || general_action.unwrap() == "prev_field") {
|
||||
let changed = TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
key_event,
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state
|
||||
);
|
||||
if changed { add_logic_state.has_unsaved_changes = true; }
|
||||
handled = true;
|
||||
} else {
|
||||
let changed = TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
key_event,
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state
|
||||
);
|
||||
if changed { add_logic_state.has_unsaved_changes = true; }
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if handled { return true; }
|
||||
}
|
||||
|
||||
// If not handled above (e.g., Tab/Shift+Tab, or Enter when script content not in edit mode),
|
||||
// process general application-level actions.
|
||||
let action_str = general_action.map(String::from);
|
||||
match action_str.as_deref() {
|
||||
Some("exit_view") | Some("cancel_action") => {
|
||||
buffer_state.update_history(AppView::Admin);
|
||||
app_state.ui.show_add_logic = false;
|
||||
*command_message = "Exited Add Logic".to_string();
|
||||
*is_edit_mode = false;
|
||||
handled = true;
|
||||
}
|
||||
Some("next_field") | Some("prev_field") => {
|
||||
let is_next = action_str.as_deref() == Some("next_field");
|
||||
let previous_focus = add_logic_state.current_focus;
|
||||
|
||||
add_logic_state.current_focus = if is_next {
|
||||
match add_logic_state.current_focus {
|
||||
AddLogicFocus::InputLogicName => AddLogicFocus::InputTargetColumn,
|
||||
AddLogicFocus::InputTargetColumn => AddLogicFocus::InputDescription,
|
||||
AddLogicFocus::InputDescription => AddLogicFocus::InputScriptContent,
|
||||
AddLogicFocus::InputScriptContent => AddLogicFocus::SaveButton,
|
||||
AddLogicFocus::SaveButton => AddLogicFocus::CancelButton,
|
||||
AddLogicFocus::CancelButton => AddLogicFocus::InputLogicName,
|
||||
}
|
||||
} else {
|
||||
match add_logic_state.current_focus {
|
||||
AddLogicFocus::InputLogicName => AddLogicFocus::CancelButton,
|
||||
AddLogicFocus::InputTargetColumn => AddLogicFocus::InputLogicName,
|
||||
AddLogicFocus::InputDescription => AddLogicFocus::InputTargetColumn,
|
||||
AddLogicFocus::InputScriptContent => AddLogicFocus::InputDescription,
|
||||
AddLogicFocus::SaveButton => AddLogicFocus::InputScriptContent,
|
||||
AddLogicFocus::CancelButton => AddLogicFocus::SaveButton,
|
||||
}
|
||||
};
|
||||
|
||||
if add_logic_state.current_focus == AddLogicFocus::InputScriptContent {
|
||||
*is_edit_mode = false;
|
||||
let mode_hint = match add_logic_state.editor_keybinding_mode {
|
||||
EditorKeybindingMode::Vim => "'i'/'a'/'o' to insert",
|
||||
_ => "Enter/Ctrl+E to edit",
|
||||
};
|
||||
*command_message = format!("Focus: Script Content. Press {} or Tab.", mode_hint);
|
||||
} else if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) {
|
||||
*is_edit_mode = true;
|
||||
*command_message = format!("Focus: {:?}. Edit mode ON.", add_logic_state.current_focus);
|
||||
} else {
|
||||
*is_edit_mode = false;
|
||||
*command_message = format!("Focus: {:?}", add_logic_state.current_focus);
|
||||
}
|
||||
|
||||
app_state.ui.focus_outside_canvas = !matches!(
|
||||
add_logic_state.current_focus,
|
||||
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent
|
||||
);
|
||||
handled = true;
|
||||
}
|
||||
Some("select") => {
|
||||
match add_logic_state.current_focus {
|
||||
AddLogicFocus::InputScriptContent => {
|
||||
*is_edit_mode = true;
|
||||
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
|
||||
match add_logic_state.editor_keybinding_mode {
|
||||
EditorKeybindingMode::Vim => {
|
||||
TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
KeyEvent::new(KeyCode::Char('i'), KeyModifiers::NONE),
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state,
|
||||
);
|
||||
*command_message = "VIM: Insert Mode.".to_string();
|
||||
}
|
||||
_ => {
|
||||
*command_message = "Entered script edit mode.".to_string();
|
||||
}
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
AddLogicFocus::SaveButton => { handled = true; }
|
||||
AddLogicFocus::CancelButton => { *is_edit_mode = false; handled = true; }
|
||||
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription => {
|
||||
*is_edit_mode = !*is_edit_mode;
|
||||
*command_message = format!("Field edit mode: {}", if *is_edit_mode { "ON" } else { "OFF" });
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some("toggle_edit_mode") => {
|
||||
match add_logic_state.current_focus {
|
||||
AddLogicFocus::InputScriptContent => {
|
||||
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
|
||||
match add_logic_state.editor_keybinding_mode {
|
||||
EditorKeybindingMode::Vim => {
|
||||
if *is_edit_mode {
|
||||
// First escape: try to go to Vim Normal mode
|
||||
TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE),
|
||||
key_event,
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state,
|
||||
);
|
||||
if TextEditor::is_vim_normal_mode(&add_logic_state.vim_state) {
|
||||
*is_edit_mode = false;
|
||||
*command_message = "VIM: Normal Mode. Tab to navigate.".to_string();
|
||||
} else {
|
||||
*command_message = "VIM: Still in Insert Mode (toggle error?).".to_string();
|
||||
*command_message = "VIM: Normal Mode. Esc again to exit script.".to_string();
|
||||
}
|
||||
} else {
|
||||
TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
KeyEvent::new(KeyCode::Char('i'), KeyModifiers::NONE),
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state,
|
||||
);
|
||||
*is_edit_mode = true;
|
||||
*command_message = "VIM: Insert Mode.".to_string();
|
||||
// Second escape: exit fullscreen
|
||||
add_logic_state.current_focus = AddLogicFocus::ScriptContentPreview;
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
*is_edit_mode = false;
|
||||
*command_message = "Exited script editing.".to_string();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
*is_edit_mode = !*is_edit_mode;
|
||||
*command_message = format!("Script edit mode: {}", if *is_edit_mode { "ON" } else { "OFF. Tab to navigate." });
|
||||
if *is_edit_mode {
|
||||
*is_edit_mode = false;
|
||||
*command_message = "Exited script edit. Esc again to exit script.".to_string();
|
||||
} else {
|
||||
// Exit fullscreen
|
||||
add_logic_state.current_focus = AddLogicFocus::ScriptContentPreview;
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
*is_edit_mode = false;
|
||||
*command_message = "Exited script editing.".to_string();
|
||||
}
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription => {
|
||||
*is_edit_mode = !*is_edit_mode;
|
||||
*command_message = format!("Canvas field edit mode: {}", if *is_edit_mode { "ON" } else { "OFF" });
|
||||
handled = true;
|
||||
return true;
|
||||
}
|
||||
_ => { *command_message = "Cannot toggle edit mode here.".to_string(); handled = true; }
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if add_logic_state.current_focus == AddLogicFocus::InputScriptContent &&
|
||||
!*is_edit_mode &&
|
||||
add_logic_state.editor_keybinding_mode == EditorKeybindingMode::Vim {
|
||||
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
|
||||
|
||||
// ALL OTHER KEYS: Pass directly to textarea without any interference
|
||||
let changed = TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
key_event,
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state
|
||||
&mut add_logic_state.vim_state,
|
||||
);
|
||||
if changed { add_logic_state.has_unsaved_changes = true; }
|
||||
handled = true;
|
||||
if changed {
|
||||
add_logic_state.has_unsaved_changes = true;
|
||||
}
|
||||
|
||||
// Update edit mode status for Vim
|
||||
if add_logic_state.editor_keybinding_mode == EditorKeybindingMode::Vim {
|
||||
*is_edit_mode = !TextEditor::is_vim_normal_mode(&add_logic_state.vim_state);
|
||||
}
|
||||
|
||||
return true; // Always consume the event in fullscreen mode
|
||||
}
|
||||
// === END FULLSCREEN ISOLATION ===
|
||||
|
||||
// Regular navigation logic for non-fullscreen elements
|
||||
let action = config.get_general_action(key_event.code, key_event.modifiers);
|
||||
let current_focus = add_logic_state.current_focus;
|
||||
let mut handled = true;
|
||||
let mut new_focus = current_focus;
|
||||
|
||||
match action.as_deref() {
|
||||
Some("exit_table_scroll") => {
|
||||
// This shouldn't happen since we handle InsideScriptContent above
|
||||
handled = false;
|
||||
}
|
||||
Some("move_up") => {
|
||||
match current_focus {
|
||||
AddLogicFocus::InputLogicName => {
|
||||
// Stay at top
|
||||
}
|
||||
AddLogicFocus::InputTargetColumn => new_focus = AddLogicFocus::InputLogicName,
|
||||
AddLogicFocus::InputDescription => new_focus = AddLogicFocus::InputTargetColumn,
|
||||
AddLogicFocus::ScriptContentPreview => new_focus = AddLogicFocus::InputDescription,
|
||||
AddLogicFocus::SaveButton => new_focus = AddLogicFocus::ScriptContentPreview,
|
||||
AddLogicFocus::CancelButton => new_focus = AddLogicFocus::SaveButton,
|
||||
_ => handled = false,
|
||||
}
|
||||
}
|
||||
Some("move_down") => {
|
||||
match current_focus {
|
||||
AddLogicFocus::InputLogicName => new_focus = AddLogicFocus::InputTargetColumn,
|
||||
AddLogicFocus::InputTargetColumn => new_focus = AddLogicFocus::InputDescription,
|
||||
AddLogicFocus::InputDescription => {
|
||||
add_logic_state.last_canvas_field = 2;
|
||||
new_focus = AddLogicFocus::ScriptContentPreview;
|
||||
},
|
||||
AddLogicFocus::ScriptContentPreview => new_focus = AddLogicFocus::SaveButton,
|
||||
AddLogicFocus::SaveButton => new_focus = AddLogicFocus::CancelButton,
|
||||
AddLogicFocus::CancelButton => {
|
||||
// Stay at bottom
|
||||
}
|
||||
_ => handled = false,
|
||||
}
|
||||
}
|
||||
Some("next_option") => {
|
||||
match current_focus {
|
||||
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription =>
|
||||
{ new_focus = AddLogicFocus::ScriptContentPreview; }
|
||||
AddLogicFocus::ScriptContentPreview => new_focus = AddLogicFocus::SaveButton,
|
||||
AddLogicFocus::SaveButton => new_focus = AddLogicFocus::CancelButton,
|
||||
AddLogicFocus::CancelButton => { /* Stay at last */ }
|
||||
_ => handled = false,
|
||||
}
|
||||
}
|
||||
Some("previous_option") => {
|
||||
match current_focus {
|
||||
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription =>
|
||||
{ /* Stay at first */ }
|
||||
AddLogicFocus::ScriptContentPreview => new_focus = AddLogicFocus::InputDescription,
|
||||
AddLogicFocus::SaveButton => new_focus = AddLogicFocus::ScriptContentPreview,
|
||||
AddLogicFocus::CancelButton => new_focus = AddLogicFocus::SaveButton,
|
||||
_ => handled = false,
|
||||
}
|
||||
}
|
||||
Some("next_field") => {
|
||||
new_focus = match current_focus {
|
||||
AddLogicFocus::InputLogicName => AddLogicFocus::InputTargetColumn,
|
||||
AddLogicFocus::InputTargetColumn => AddLogicFocus::InputDescription,
|
||||
AddLogicFocus::InputDescription => AddLogicFocus::ScriptContentPreview,
|
||||
AddLogicFocus::ScriptContentPreview => AddLogicFocus::SaveButton,
|
||||
AddLogicFocus::SaveButton => AddLogicFocus::CancelButton,
|
||||
AddLogicFocus::CancelButton => AddLogicFocus::InputLogicName,
|
||||
_ => current_focus,
|
||||
};
|
||||
}
|
||||
Some("prev_field") => {
|
||||
new_focus = match current_focus {
|
||||
AddLogicFocus::InputLogicName => AddLogicFocus::CancelButton,
|
||||
AddLogicFocus::InputTargetColumn => AddLogicFocus::InputLogicName,
|
||||
AddLogicFocus::InputDescription => AddLogicFocus::InputTargetColumn,
|
||||
AddLogicFocus::ScriptContentPreview => AddLogicFocus::InputDescription,
|
||||
AddLogicFocus::SaveButton => AddLogicFocus::ScriptContentPreview,
|
||||
AddLogicFocus::CancelButton => AddLogicFocus::SaveButton,
|
||||
_ => current_focus,
|
||||
};
|
||||
}
|
||||
Some("select") => {
|
||||
match current_focus {
|
||||
AddLogicFocus::ScriptContentPreview => {
|
||||
new_focus = AddLogicFocus::InsideScriptContent;
|
||||
*is_edit_mode = false; // Start in preview mode
|
||||
app_state.ui.focus_outside_canvas = false; // Script is like canvas
|
||||
let mode_hint = match add_logic_state.editor_keybinding_mode {
|
||||
EditorKeybindingMode::Vim => "VIM mode - 'i'/'a'/'o' to edit",
|
||||
_ => "Enter/Ctrl+E to edit",
|
||||
};
|
||||
*command_message = format!("Fullscreen script editing. {} or Esc to exit.", mode_hint);
|
||||
}
|
||||
AddLogicFocus::SaveButton => {
|
||||
*command_message = "Save logic action".to_string();
|
||||
}
|
||||
AddLogicFocus::CancelButton => {
|
||||
buffer_state.update_history(AppView::Admin);
|
||||
app_state.ui.show_add_logic = false;
|
||||
*command_message = "Cancelled Add Logic".to_string();
|
||||
*is_edit_mode = false;
|
||||
}
|
||||
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription => {
|
||||
*is_edit_mode = !*is_edit_mode;
|
||||
*command_message = format!("Field edit mode: {}", if *is_edit_mode { "ON" } else { "OFF" });
|
||||
}
|
||||
_ => handled = false,
|
||||
}
|
||||
}
|
||||
Some("toggle_edit_mode") => {
|
||||
match current_focus {
|
||||
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription => {
|
||||
*is_edit_mode = !*is_edit_mode;
|
||||
*command_message = format!("Canvas field edit mode: {}", if *is_edit_mode { "ON" } else { "OFF" });
|
||||
}
|
||||
_ => {
|
||||
*command_message = "Cannot toggle edit mode here.".to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => handled = false,
|
||||
}
|
||||
|
||||
if handled && current_focus != new_focus {
|
||||
add_logic_state.current_focus = new_focus;
|
||||
|
||||
// Set edit mode and canvas focus based on new focus
|
||||
let new_is_canvas_input_focus = matches!(new_focus,
|
||||
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription
|
||||
);
|
||||
|
||||
if new_is_canvas_input_focus {
|
||||
// Entering canvas - start in readonly mode
|
||||
*is_edit_mode = false;
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
} else {
|
||||
// Outside canvas
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
if matches!(new_focus, AddLogicFocus::ScriptContentPreview) {
|
||||
*is_edit_mode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handled
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::config::binds::config::Config;
|
||||
use crate::state::app::buffer::{BufferState, AppView};
|
||||
use crate::state::pages::add_table::{AddTableState, LinkDefinition};
|
||||
use ratatui::widgets::ListState;
|
||||
use crate::state::pages::add_logic::AddLogicState;
|
||||
use crate::state::pages::add_logic::{AddLogicState, AddLogicFocus}; // Added AddLogicFocus import
|
||||
|
||||
// Helper functions list_select_next and list_select_previous remain the same
|
||||
fn list_select_next(list_state: &mut ListState, item_count: usize) {
|
||||
@@ -90,11 +90,11 @@ pub fn handle_admin_navigation(
|
||||
}
|
||||
Some("select") => {
|
||||
admin_state.selected_profile_index = admin_state.profile_list_state.selected();
|
||||
admin_state.selected_table_index = None;
|
||||
admin_state.selected_table_index = None; // Deselect table when profile changes
|
||||
if let Some(profile_idx) = admin_state.selected_profile_index {
|
||||
if let Some(profile) = app_state.profile_tree.profiles.get(profile_idx) {
|
||||
if !profile.tables.is_empty() {
|
||||
admin_state.table_list_state.select(Some(0));
|
||||
admin_state.table_list_state.select(Some(0)); // Auto-select first table for nav
|
||||
} else {
|
||||
admin_state.table_list_state.select(None);
|
||||
}
|
||||
@@ -147,7 +147,7 @@ pub fn handle_admin_navigation(
|
||||
} else {
|
||||
*command_message = "No tables in selected profile.".to_string();
|
||||
}
|
||||
admin_state.current_focus = AdminFocus::Tables;
|
||||
admin_state.current_focus = AdminFocus::Tables; // Stay in Tables pane if no tables to enter
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
@@ -205,10 +205,9 @@ pub fn handle_admin_navigation(
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
Some("select") => {
|
||||
Some("select") => { // This is for persistently selecting a table with [*]
|
||||
admin_state.selected_table_index = admin_state.table_list_state.selected();
|
||||
let table_name = admin_state.selected_profile_index
|
||||
.or_else(|| admin_state.profile_list_state.selected())
|
||||
.and_then(|p_idx| app_state.profile_tree.profiles.get(p_idx))
|
||||
.and_then(|p| admin_state.selected_table_index.and_then(|t_idx| p.tables.get(t_idx)))
|
||||
.map_or("N/A", |t| t.name.as_str());
|
||||
@@ -224,30 +223,44 @@ pub fn handle_admin_navigation(
|
||||
}
|
||||
}
|
||||
|
||||
AdminFocus::Button1 => {
|
||||
AdminFocus::Button1 => { // Add Logic Button
|
||||
match action.as_deref() {
|
||||
Some("select") => {
|
||||
let mut logic_state_profile_name = "None (Global)".to_string();
|
||||
let mut selected_table_name_for_logic: Option<String> = None;
|
||||
Some("select") => { // Typically "Enter" key
|
||||
if let Some(p_idx) = admin_state.selected_profile_index {
|
||||
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
||||
logic_state_profile_name = profile.name.clone();
|
||||
if let Some(t_idx) = admin_state.selected_table_index {
|
||||
if let Some(table) = profile.tables.get(t_idx) {
|
||||
selected_table_name_for_logic = Some(table.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Both profile and table are selected, proceed
|
||||
admin_state.add_logic_state = AddLogicState {
|
||||
profile_name: logic_state_profile_name.clone(),
|
||||
selected_table_name: selected_table_name_for_logic,
|
||||
profile_name: profile.name.clone(),
|
||||
selected_table_name: Some(table.name.clone()),
|
||||
// selected_table_id: table.id, // If you have table IDs
|
||||
editor_keybinding_mode: config.editor.keybinding_mode.clone(),
|
||||
current_focus: AddLogicFocus::default(), // Reset focus for the new screen
|
||||
..AddLogicState::default()
|
||||
};
|
||||
buffer_state.update_history(AppView::AddLogic);
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
*command_message = "Opening Add Logic...".to_string();
|
||||
buffer_state.update_history(AppView::AddLogic); // Switch view
|
||||
app_state.ui.focus_outside_canvas = false; // Ensure canvas focus
|
||||
*command_message = format!(
|
||||
"Opening Add Logic for table '{}' in profile '{}'...",
|
||||
table.name, profile.name
|
||||
);
|
||||
} else {
|
||||
// This case should ideally not be reached if indices are managed correctly
|
||||
*command_message = "Error: Selected table data not found.".to_string();
|
||||
}
|
||||
} else {
|
||||
// Profile is selected, but table is not
|
||||
*command_message = "Select a table first!".to_string();
|
||||
}
|
||||
} else {
|
||||
// This case should ideally not be reached if p_idx is valid
|
||||
*command_message = "Error: Selected profile data not found.".to_string();
|
||||
}
|
||||
} else {
|
||||
// Profile is not selected
|
||||
*command_message = "Select a profile first!".to_string();
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
Some("previous_option") | Some("move_up") => {
|
||||
@@ -264,20 +277,24 @@ pub fn handle_admin_navigation(
|
||||
}
|
||||
}
|
||||
|
||||
AdminFocus::Button2 => {
|
||||
AdminFocus::Button2 => { // Add Table Button
|
||||
match action.as_deref() {
|
||||
Some("select") => {
|
||||
if let Some(p_idx) = admin_state.selected_profile_index {
|
||||
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
||||
let selected_profile_name = profile.name.clone();
|
||||
// Prepare links from the selected profile's existing tables
|
||||
let available_links: Vec<LinkDefinition> = profile.tables.iter()
|
||||
.map(|table| LinkDefinition {
|
||||
linked_table_name: table.name.clone(),
|
||||
is_required: false, selected: false,
|
||||
is_required: false, // Default, can be changed in AddTable screen
|
||||
selected: false,
|
||||
}).collect();
|
||||
|
||||
admin_state.add_table_state = AddTableState {
|
||||
profile_name: selected_profile_name, links: available_links,
|
||||
..AddTableState::default()
|
||||
profile_name: selected_profile_name,
|
||||
links: available_links,
|
||||
..AddTableState::default() // Reset other fields
|
||||
};
|
||||
buffer_state.update_history(AppView::AddTable);
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
@@ -306,9 +323,10 @@ pub fn handle_admin_navigation(
|
||||
}
|
||||
}
|
||||
|
||||
AdminFocus::Button3 => {
|
||||
AdminFocus::Button3 => { // Change Table Button
|
||||
match action.as_deref() {
|
||||
Some("select") => {
|
||||
// Future: Logic to load selected table into AddTableState for editing
|
||||
*command_message = "Action: Change Table (Not Implemented)".to_string();
|
||||
handled = true;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ fn find_prev_word_end(text: &str, current_pos: usize) -> usize {
|
||||
pub async fn execute_action(
|
||||
action: &str,
|
||||
app_state: &mut AppState,
|
||||
state: &mut AddLogicState, // Changed
|
||||
state: &mut AddLogicState,
|
||||
ideal_cursor_column: &mut usize,
|
||||
key_sequence_tracker: &mut KeySequenceTracker,
|
||||
command_message: &mut String,
|
||||
@@ -75,7 +75,7 @@ pub async fn execute_action(
|
||||
match action {
|
||||
"move_up" => {
|
||||
key_sequence_tracker.reset();
|
||||
let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed
|
||||
let num_fields = AddLogicState::INPUT_FIELD_COUNT;
|
||||
if num_fields == 0 { return Ok("No fields.".to_string()); }
|
||||
let current_field = state.current_field();
|
||||
|
||||
@@ -87,20 +87,13 @@ pub async fn execute_action(
|
||||
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
||||
state.set_current_cursor_pos(new_pos);
|
||||
} else {
|
||||
// Moving up from the first field (InputLogicName)
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
// Focus should go to the element logically above the canvas.
|
||||
// Based on AddLogicFocus, this might be CancelButton or another element.
|
||||
// For AddLogic, let's assume it's CancelButton, similar to AddTable.
|
||||
state.current_focus = crate::state::pages::add_logic::AddLogicFocus::CancelButton; // Changed
|
||||
key_sequence_tracker.reset();
|
||||
return Ok("Focus moved above canvas".to_string());
|
||||
*command_message = "At top of form.".to_string();
|
||||
}
|
||||
Ok("".to_string())
|
||||
Ok(command_message.clone())
|
||||
}
|
||||
"move_down" => {
|
||||
key_sequence_tracker.reset();
|
||||
let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed
|
||||
let num_fields = AddLogicState::INPUT_FIELD_COUNT;
|
||||
if num_fields == 0 { return Ok("No fields.".to_string()); }
|
||||
let current_field = state.current_field();
|
||||
let last_field_index = num_fields - 1;
|
||||
@@ -113,21 +106,19 @@ pub async fn execute_action(
|
||||
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
||||
state.set_current_cursor_pos(new_pos);
|
||||
} else {
|
||||
// Moving down from the last field (InputDescription)
|
||||
// Move focus outside canvas when moving down from the last field
|
||||
// FIX: Go to ScriptContentPreview instead of SaveButton
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
// Focus should go to the element logically below the canvas.
|
||||
// This is likely InputScriptContent or SaveButton.
|
||||
// The add_logic_nav.rs handles transitions to InputScriptContent.
|
||||
// If moving from canvas directly to buttons, it would be SaveButton.
|
||||
state.current_focus = crate::state::pages::add_logic::AddLogicFocus::InputScriptContent; // Or SaveButton
|
||||
key_sequence_tracker.reset();
|
||||
return Ok("Focus moved to script/button area".to_string());
|
||||
state.last_canvas_field = 2;
|
||||
state.current_focus = crate::state::pages::add_logic::AddLogicFocus::ScriptContentPreview; // FIXED!
|
||||
*command_message = "Focus moved to script preview".to_string();
|
||||
}
|
||||
Ok("".to_string())
|
||||
Ok(command_message.clone())
|
||||
}
|
||||
// ... (rest of the actions remain the same) ...
|
||||
"move_first_line" => {
|
||||
key_sequence_tracker.reset();
|
||||
if AddLogicState::INPUT_FIELD_COUNT > 0 { // Changed
|
||||
if AddLogicState::INPUT_FIELD_COUNT > 0 {
|
||||
state.set_current_field(0);
|
||||
let current_input = state.get_current_input();
|
||||
let max_cursor_pos = if current_input.is_empty() { 0 } else { current_input.len().saturating_sub(1) };
|
||||
@@ -139,7 +130,7 @@ pub async fn execute_action(
|
||||
}
|
||||
"move_last_line" => {
|
||||
key_sequence_tracker.reset();
|
||||
let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed
|
||||
let num_fields = AddLogicState::INPUT_FIELD_COUNT;
|
||||
if num_fields > 0 {
|
||||
let last_field_index = num_fields - 1;
|
||||
state.set_current_field(last_field_index);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/state/pages/add_logic.rs
|
||||
use crate::config::binds::config::{EditorConfig, EditorKeybindingMode};
|
||||
use crate::state::pages::canvas_state::CanvasState;
|
||||
use crate::components::common::text_editor::{TextEditor, VimState}; // Add VimState import
|
||||
use crate::components::common::text_editor::{TextEditor, VimState};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use tui_textarea::TextArea;
|
||||
@@ -11,8 +11,9 @@ pub enum AddLogicFocus {
|
||||
#[default]
|
||||
InputLogicName,
|
||||
InputTargetColumn,
|
||||
InputScriptContent,
|
||||
InputDescription,
|
||||
ScriptContentPreview, // Like ColumnsTable - can be highlighted/selected
|
||||
InsideScriptContent, // Like InsideColumnsTable - full editing mode
|
||||
SaveButton,
|
||||
CancelButton,
|
||||
}
|
||||
@@ -27,12 +28,13 @@ pub struct AddLogicState {
|
||||
pub script_content_editor: Rc<RefCell<TextArea<'static>>>,
|
||||
pub description_input: String,
|
||||
pub current_focus: AddLogicFocus,
|
||||
pub last_canvas_field: usize,
|
||||
pub logic_name_cursor_pos: usize,
|
||||
pub target_column_cursor_pos: usize,
|
||||
pub description_cursor_pos: usize,
|
||||
pub has_unsaved_changes: bool,
|
||||
pub editor_keybinding_mode: EditorKeybindingMode,
|
||||
pub vim_state: VimState, // Add this field
|
||||
pub vim_state: VimState,
|
||||
}
|
||||
|
||||
impl AddLogicState {
|
||||
@@ -47,12 +49,13 @@ impl AddLogicState {
|
||||
script_content_editor: Rc::new(RefCell::new(editor)),
|
||||
description_input: String::new(),
|
||||
current_focus: AddLogicFocus::InputLogicName,
|
||||
last_canvas_field: 2,
|
||||
logic_name_cursor_pos: 0,
|
||||
target_column_cursor_pos: 0,
|
||||
description_cursor_pos: 0,
|
||||
has_unsaved_changes: false,
|
||||
editor_keybinding_mode: editor_config.keybinding_mode.clone(),
|
||||
vim_state: VimState::default(), // Add this field initialization
|
||||
vim_state: VimState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,14 +68,13 @@ impl Default for AddLogicState {
|
||||
}
|
||||
}
|
||||
|
||||
// ... rest of the CanvasState implementation remains the same
|
||||
impl CanvasState for AddLogicState {
|
||||
fn current_field(&self) -> usize {
|
||||
match self.current_focus {
|
||||
AddLogicFocus::InputLogicName => 0,
|
||||
AddLogicFocus::InputTargetColumn => 1,
|
||||
AddLogicFocus::InputDescription => 2,
|
||||
_ => 0,
|
||||
_ => self.last_canvas_field,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,9 +123,18 @@ impl CanvasState for AddLogicState {
|
||||
|
||||
fn set_current_field(&mut self, index: usize) {
|
||||
self.current_focus = match index {
|
||||
0 => AddLogicFocus::InputLogicName,
|
||||
1 => AddLogicFocus::InputTargetColumn,
|
||||
2 => AddLogicFocus::InputDescription,
|
||||
0 => {
|
||||
self.last_canvas_field = 0;
|
||||
AddLogicFocus::InputLogicName
|
||||
},
|
||||
1 => {
|
||||
self.last_canvas_field = 1;
|
||||
AddLogicFocus::InputTargetColumn
|
||||
},
|
||||
2 => {
|
||||
self.last_canvas_field = 2;
|
||||
AddLogicFocus::InputDescription
|
||||
},
|
||||
_ => self.current_focus,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user