text area working now perfectly well

This commit is contained in:
filipriec
2025-05-24 16:34:59 +02:00
parent a874edf2a1
commit 56fe1c2ccc
5 changed files with 249 additions and 376 deletions

69
Cargo.lock generated
View File

@@ -402,7 +402,7 @@ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"common", "common",
"crossterm 0.29.0", "crossterm",
"dirs 6.0.0", "dirs 6.0.0",
"dotenvy", "dotenvy",
"lazy_static", "lazy_static",
@@ -485,15 +485,6 @@ version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2459fc9262a1aa204eb4b5764ad4f189caec88aea9634389c0a25f8be7f6265e" checksum = "2459fc9262a1aa204eb4b5764ad4f189caec88aea9634389c0a25f8be7f6265e"
[[package]]
name = "convert_case"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@@ -621,24 +612,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "crossterm"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
dependencies = [
"bitflags",
"crossterm_winapi",
"derive_more",
"document-features",
"mio",
"parking_lot",
"rustix 1.0.5",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]] [[package]]
name = "crossterm_winapi" name = "crossterm_winapi"
version = "0.9.1" version = "0.9.1"
@@ -727,27 +700,6 @@ dependencies = [
"powerfmt", "powerfmt",
] ]
[[package]]
name = "derive_more"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@@ -813,15 +765,6 @@ dependencies = [
"syn 2.0.100", "syn 2.0.100",
] ]
[[package]]
name = "document-features"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
dependencies = [
"litrs",
]
[[package]] [[package]]
name = "dotenvy" name = "dotenvy"
version = "0.15.7" version = "0.15.7"
@@ -1682,12 +1625,6 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
[[package]]
name = "litrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.12" version = "0.4.12"
@@ -2355,7 +2292,7 @@ dependencies = [
"bitflags", "bitflags",
"cassowary", "cassowary",
"compact_str", "compact_str",
"crossterm 0.28.1", "crossterm",
"indoc", "indoc",
"instability", "instability",
"itertools 0.13.0", "itertools 0.13.0",
@@ -3613,7 +3550,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae" checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae"
dependencies = [ dependencies = [
"crossterm 0.28.1", "crossterm",
"ratatui", "ratatui",
"unicode-width 0.2.0", "unicode-width 0.2.0",
] ]

View File

@@ -9,12 +9,12 @@ anyhow = "1.0.98"
async-trait = "0.1.88" async-trait = "0.1.88"
common = { path = "../common" } common = { path = "../common" }
crossterm = "0.29.0" crossterm = "0.28.1"
dirs = "6.0.0" dirs = "6.0.0"
dotenvy = "0.15.7" dotenvy = "0.15.7"
lazy_static = "1.5.0" lazy_static = "1.5.0"
prost = "0.13.5" prost = "0.13.5"
ratatui = "0.29.0" ratatui = { version = "0.29.0", features = ["crossterm"] }
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
time = "0.3.41" time = "0.3.41"
tokio = { version = "1.44.2", features = ["full", "macros"] } tokio = { version = "1.44.2", features = ["full", "macros"] }
@@ -22,6 +22,6 @@ toml = "0.8.20"
tonic = "0.13.0" tonic = "0.13.0"
tracing = "0.1.41" tracing = "0.1.41"
tracing-subscriber = "0.3.19" tracing-subscriber = "0.3.19"
tui-textarea = "0.7.0" tui-textarea = { version = "0.7.0", features = ["crossterm"] }
unicode-segmentation = "1.12.0" unicode-segmentation = "1.12.0"
unicode-width = "0.2.0" unicode-width = "0.2.0"

View File

@@ -6,9 +6,9 @@ use crate::state::pages::add_logic::{AddLogicFocus, AddLogicState};
use crate::state::pages::canvas_state::CanvasState; use crate::state::pages::canvas_state::CanvasState;
use ratatui::{ use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect}, layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Modifier, Style}, style::{Modifier, Style, Stylize}, // Added Stylize for .dim()
text::{Line, Span, Text}, text::{Line, Span},
widgets::{Block, BorderType, Borders, Paragraph}, widgets::{Block, BorderType, Borders, Paragraph}, // Removed unused Widget
Frame, Frame,
}; };
use crate::components::handlers::canvas::render_canvas; use crate::components::handlers::canvas::render_canvas;
@@ -33,39 +33,43 @@ pub fn render_add_logic(
let inner_area = main_block.inner(area); let inner_area = main_block.inner(area);
f.render_widget(main_block, area); f.render_widget(main_block, area);
// --- Fullscreen Script Content Check ---
if add_logic_state.current_focus == AddLogicFocus::InputScriptContent { if add_logic_state.current_focus == AddLogicFocus::InputScriptContent {
let script_block_border_style = Style::default().fg(theme.highlight); // Always highlighted let mut editor = add_logic_state.script_content_editor.borrow_mut();
let border_style = if is_edit_mode {
Style::default().fg(theme.highlight)
} else {
Style::default().fg(theme.highlight)
};
let script_block = Block::default() editor.set_cursor_line_style(
.title(Span::styled( Style::default().bg(theme.secondary),
" Steel Script Content (Fullscreen) ", );
theme.fg, editor.set_line_number_style(
)) // Indicate fullscreen Style::default().fg(theme.secondary),
.title_alignment(Alignment::Center) );
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(script_block_border_style);
let script_text = editor.set_block(
Text::from(add_logic_state.script_content_input.as_str()); Block::default()
let script_paragraph = Paragraph::new(script_text) .title(Span::styled(
.block(script_block) " Steel Script Content (Ctrl+E or Enter to edit, Esc to unfocus/exit edit) ",
.scroll(add_logic_state.script_content_scroll) Style::default().fg(theme.fg),
.style(Style::default().fg(theme.fg)); ))
f.render_widget(script_paragraph, inner_area); // Use inner_area for fullscreen .title_alignment(Alignment::Center)
return; // IMPORTANT: Stop rendering here for fullscreen mode .borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(border_style),
);
f.render_widget(&*editor, inner_area);
return;
} }
// --- Normal Layout ---
// Calculate areas dynamically
let main_chunks = Layout::default() let main_chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([ .constraints([
Constraint::Length(3), // Top Info (Profile/Table) Constraint::Length(3),
Constraint::Length(9), // Canvas Area (3 input fields × 3 lines each) Constraint::Length(9),
Constraint::Min(5), // Script Content Area Constraint::Min(5),
Constraint::Length(3), // Bottom Buttons Constraint::Length(3),
]) ])
.split(inner_area); .split(inner_area);
@@ -74,21 +78,22 @@ pub fn render_add_logic(
let script_content_area = main_chunks[2]; let script_content_area = main_chunks[2];
let buttons_area = main_chunks[3]; let buttons_area = main_chunks[3];
// Top Info Rendering
let profile_text = Paragraph::new(vec![ let profile_text = Paragraph::new(vec![
Line::from(Span::styled( Line::from(Span::styled(
format!("Profile: {}", add_logic_state.profile_name), format!("Profile: {}", add_logic_state.profile_name),
theme.fg, Style::default().fg(theme.fg),
)), )),
Line::from(Span::styled( Line::from(Span::styled(
format!( format!(
"Table: {}", "Table: {}",
add_logic_state add_logic_state
.selected_table_id .selected_table_name
.map(|id| format!("ID {}", id)) .clone()
.unwrap_or_else(|| "Global".to_string()) .unwrap_or_else(|| add_logic_state.selected_table_id
.map(|id| format!("ID {}", id))
.unwrap_or_else(|| "Global (Not Selected)".to_string()))
), ),
theme.fg, Style::default().fg(theme.fg),
)), )),
]) ])
.block( .block(
@@ -98,7 +103,6 @@ pub fn render_add_logic(
); );
f.render_widget(profile_text, top_info_area); f.render_widget(profile_text, top_info_area);
// Canvas rendering for input fields
let focus_on_canvas_inputs = matches!( let focus_on_canvas_inputs = matches!(
add_logic_state.current_focus, add_logic_state.current_focus,
AddLogicFocus::InputLogicName AddLogicFocus::InputLogicName
@@ -118,29 +122,23 @@ pub fn render_add_logic(
highlight_state, highlight_state,
); );
// Script Content Area (Normal Mode) {
let script_block_border_style = let mut editor = add_logic_state.script_content_editor.borrow_mut();
if add_logic_state.current_focus == AddLogicFocus::InputScriptContent { editor.set_cursor_line_style(Style::default());
Style::default().fg(theme.highlight) editor.set_line_number_style(
} else { Style::default().fg(theme.secondary).dim(), // Fixed: apply .dim() to Style, not Color
Style::default().fg(theme.secondary) );
};
let script_block = Block::default() editor.set_block(
.title(" Steel Script Content ") Block::default()
.borders(Borders::ALL) .title(" Steel Script Content ")
.border_type(BorderType::Rounded) .borders(Borders::ALL)
.border_style(script_block_border_style); .border_type(BorderType::Rounded)
.border_style(Style::default().fg(theme.secondary)),
);
f.render_widget(&*editor, script_content_area);
}
let script_text =
Text::from(add_logic_state.script_content_input.as_str());
let script_paragraph = Paragraph::new(script_text)
.block(script_block)
.scroll(add_logic_state.script_content_scroll)
.style(Style::default().fg(theme.fg));
f.render_widget(script_paragraph, script_content_area);
// Button Style Helpers
let get_button_style = |button_focus: AddLogicFocus, current_focus| { let get_button_style = |button_focus: AddLogicFocus, current_focus| {
let is_focused = current_focus == button_focus; let is_focused = current_focus == button_focus;
let base_style = Style::default().fg(if is_focused { let base_style = Style::default().fg(if is_focused {
@@ -163,12 +161,11 @@ pub fn render_add_logic(
} }
}; };
// Bottom Buttons
let button_chunks = Layout::default() let button_chunks = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints([ .constraints([
Constraint::Percentage(50), // Save Button Constraint::Percentage(50),
Constraint::Percentage(50), // Cancel Button Constraint::Percentage(50),
]) ])
.split(buttons_area); .split(buttons_area);
@@ -206,7 +203,6 @@ pub fn render_add_logic(
); );
f.render_widget(cancel_button, button_chunks[1]); f.render_widget(cancel_button, button_chunks[1]);
// Dialog rendering
if app_state.ui.dialog.dialog_show { if app_state.ui.dialog.dialog_show {
dialog::render_dialog( dialog::render_dialog(
f, f,

View File

@@ -1,4 +1,4 @@
// client/src/functions/modes/navigation/add_logic_nav.rs // src/functions/modes/navigation/add_logic_nav.rs
use crate::config::binds::config::Config; use crate::config::binds::config::Config;
use crate::state::{ use crate::state::{
app::state::AppState, app::state::AppState,
@@ -6,17 +6,19 @@ use crate::state::{
app::buffer::AppView, app::buffer::AppView,
app::buffer::BufferState, app::buffer::BufferState,
}; };
use crate::state::pages::canvas_state::CanvasState; // Now that client/Cargo.toml uses crossterm 0.28.1,
use crossterm::event::{KeyEvent}; // this KeyEvent will match what ratatui and tui-textarea expect.
use crossterm::event::{KeyEvent, KeyCode, KeyModifiers};
use crate::services::GrpcClient; use crate::services::GrpcClient;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use anyhow::Result; use anyhow::Result;
use common::proto::multieko2::table_script::{PostTableScriptRequest}; use common::proto::multieko2::table_script::PostTableScriptRequest;
use tui_textarea::Input as TextAreaInput; // Import with an alias
pub type SaveLogicResultSender = mpsc::Sender<Result<String>>; pub type SaveLogicResultSender = mpsc::Sender<Result<String>>;
pub fn handle_add_logic_navigation( pub fn handle_add_logic_navigation(
key: KeyEvent, key_event: KeyEvent, // This is crossterm::event::KeyEvent v0.28.1
config: &Config, config: &Config,
app_state: &mut AppState, app_state: &mut AppState,
add_logic_state: &mut AddLogicState, add_logic_state: &mut AddLogicState,
@@ -26,250 +28,180 @@ pub fn handle_add_logic_navigation(
save_logic_sender: SaveLogicResultSender, save_logic_sender: SaveLogicResultSender,
command_message: &mut String, command_message: &mut String,
) -> bool { ) -> bool {
let action = config.get_general_action(key.code, key.modifiers).map(String::from); let action = config.get_general_action(key_event.code, key_event.modifiers).map(String::from);
let mut handled = false; let mut handled = false;
// Check if focus is on canvas input fields if add_logic_state.current_focus == AddLogicFocus::InputScriptContent {
let focus_on_canvas_inputs = matches!( // Add explicit type annotation for .into()
add_logic_state.current_focus, let textarea_input: TextAreaInput = key_event.into();
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription
);
// Handle script content editing separately (multiline) if *is_edit_mode {
if *is_edit_mode && add_logic_state.current_focus == AddLogicFocus::InputScriptContent { if key_event.code == KeyCode::Esc && key_event.modifiers == KeyModifiers::NONE {
match key.code { *is_edit_mode = false;
crossterm::event::KeyCode::Char(c) => { *command_message = "Exited script edit mode. Press Ctrl+E/Enter to re-enter.".to_string();
add_logic_state.script_content_input.push(c); return true;
add_logic_state.has_unsaved_changes = true;
handled = true;
} }
crossterm::event::KeyCode::Enter => {
add_logic_state.script_content_input.push('\n'); let changed = add_logic_state.script_content_editor.borrow_mut().input(textarea_input);
if changed {
add_logic_state.has_unsaved_changes = true; add_logic_state.has_unsaved_changes = true;
add_logic_state.script_content_scroll.0 = add_logic_state.script_content_scroll.0.saturating_add(1);
handled = true;
} }
crossterm::event::KeyCode::Backspace => { handled = true;
if !add_logic_state.script_content_input.is_empty() { } else {
add_logic_state.script_content_input.pop(); match key_event.code {
add_logic_state.has_unsaved_changes = true; KeyCode::Enter if key_event.modifiers == KeyModifiers::NONE => {
*is_edit_mode = true;
*command_message = "Entered script edit mode.".to_string();
handled = true;
}
KeyCode::Up | KeyCode::Down | KeyCode::PageUp | KeyCode::PageDown |
KeyCode::Left | KeyCode::Right | KeyCode::Home | KeyCode::End => {
let changed = add_logic_state.script_content_editor.borrow_mut().input(textarea_input);
if changed {
add_logic_state.has_unsaved_changes = true;
}
handled = true;
}
KeyCode::Esc if key_event.modifiers == KeyModifiers::NONE => {
add_logic_state.current_focus = AddLogicFocus::InputDescription;
app_state.ui.focus_outside_canvas = false;
*command_message = "Script content unfocused.".to_string();
*is_edit_mode = matches!(add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription);
handled = true;
}
_ => {}
}
}
if handled { return true; }
}
// ... (rest of the function remains the same)
match action.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();
handled = true;
}
Some("next_field") => {
let previous_focus = add_logic_state.current_focus;
add_logic_state.current_focus = 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,
};
if previous_focus == AddLogicFocus::InputScriptContent &&
add_logic_state.current_focus != AddLogicFocus::InputScriptContent {
*is_edit_mode = false;
}
*is_edit_mode = matches!(add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription);
app_state.ui.focus_outside_canvas = !matches!(
add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent
);
*command_message = format!("Focus: {:?}", add_logic_state.current_focus);
handled = true;
}
Some("prev_field") => {
let previous_focus = add_logic_state.current_focus;
add_logic_state.current_focus = 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 previous_focus == AddLogicFocus::InputScriptContent &&
add_logic_state.current_focus != AddLogicFocus::InputScriptContent {
*is_edit_mode = false;
}
*is_edit_mode = matches!(add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription);
app_state.ui.focus_outside_canvas = !matches!(
add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent
);
*command_message = format!("Focus: {:?}", add_logic_state.current_focus);
handled = true;
}
Some("select") => {
match add_logic_state.current_focus {
AddLogicFocus::SaveButton => {
if let Some(table_def_id) = add_logic_state.selected_table_id {
let script_lines = add_logic_state.script_content_editor.borrow().lines().to_vec();
let script_content = script_lines.join("\n");
if add_logic_state.target_column_input.trim().is_empty() {
*command_message = "Cannot save: Target Column cannot be empty.".to_string();
} else if script_content.trim().is_empty() {
*command_message = "Cannot save: Script Content cannot be empty.".to_string();
} else {
*command_message = "Saving logic script...".to_string();
app_state.show_loading_dialog("Saving Script", "Please wait...");
let request = PostTableScriptRequest {
table_definition_id: table_def_id,
target_column: add_logic_state.target_column_input.trim().to_string(),
script: script_content.trim().to_string(),
description: add_logic_state.description_input.trim().to_string(),
};
let mut client_clone = grpc_client.clone();
let sender_clone = save_logic_sender.clone();
tokio::spawn(async move {
let result = client_clone.post_table_script(request).await
.map(|res| format!("Script saved with ID: {}", res.id))
.map_err(|e| anyhow::anyhow!("gRPC call failed: {}", e));
if sender_clone.send(result).await.is_err() {
// Log error or handle if receiver dropped
}
});
}
} else {
*command_message = "Cannot save: Table Definition ID is missing.".to_string();
}
handled = true;
}
AddLogicFocus::CancelButton => {
buffer_state.update_history(AppView::Admin);
app_state.ui.show_add_logic = false;
*command_message = "Cancelled Add Logic".to_string();
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;
}
AddLogicFocus::InputScriptContent => {
if !*is_edit_mode {
*is_edit_mode = true;
*command_message = "Entered script edit mode.".to_string();
}
handled = true; handled = true;
} }
} }
_ => {}
} }
} Some("toggle_edit_mode") => {
match add_logic_state.current_focus {
if !handled { AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent => {
match action.as_deref() { *is_edit_mode = !*is_edit_mode;
Some("exit_view") | Some("cancel_action") => { *command_message = format!("Edit mode: {}", if *is_edit_mode { "ON" } else { "OFF" });
buffer_state.update_history(AppView::Admin); // Fixed: was AdminPanel
app_state.ui.focus_outside_canvas = true;
*command_message = "Exited Add Logic".to_string();
handled = true;
}
Some("next_field") => {
let previous_focus = add_logic_state.current_focus;
add_logic_state.current_focus = 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,
};
// Update canvas field index only when moving between canvas inputs
if matches!(previous_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn) {
if matches!(add_logic_state.current_focus, AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) {
let new_field = match add_logic_state.current_focus {
AddLogicFocus::InputTargetColumn => 1,
AddLogicFocus::InputDescription => 2,
_ => 0,
};
add_logic_state.set_current_field(new_field);
}
} }
_ => {
// Update focus outside canvas flag *command_message = "Cannot toggle edit mode here.".to_string();
app_state.ui.focus_outside_canvas = !matches!(
add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription
);
*command_message = format!("Focus: {:?}", add_logic_state.current_focus);
*is_edit_mode = matches!(add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn |
AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent);
handled = true;
}
Some("prev_field") => {
let previous_focus = add_logic_state.current_focus;
add_logic_state.current_focus = 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,
};
// Update canvas field index only when moving between canvas inputs
if matches!(previous_focus, AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) {
if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn) {
let new_field = match add_logic_state.current_focus {
AddLogicFocus::InputLogicName => 0,
AddLogicFocus::InputTargetColumn => 1,
_ => 0,
};
add_logic_state.set_current_field(new_field);
}
}
// Update focus outside canvas flag
app_state.ui.focus_outside_canvas = !matches!(
add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription
);
*command_message = format!("Focus: {:?}", add_logic_state.current_focus);
*is_edit_mode = matches!(add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn |
AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent);
handled = true;
}
Some("next_option") => { // Horizontal next
let previous_focus = add_logic_state.current_focus;
add_logic_state.current_focus = 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, // Cycle back
};
// Update canvas field index if moving within canvas inputs
if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) {
let new_field = match add_logic_state.current_focus {
AddLogicFocus::InputLogicName => 0,
AddLogicFocus::InputTargetColumn => 1,
AddLogicFocus::InputDescription => 2,
_ => add_logic_state.current_field(), // Should not happen
};
add_logic_state.set_current_field(new_field);
}
app_state.ui.focus_outside_canvas = !matches!(
add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription
);
*command_message = format!("Focus: {:?}", add_logic_state.current_focus);
*is_edit_mode = matches!(add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn |
AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent);
handled = true;
}
Some("previous_option") => { // Horizontal previous
let previous_focus = add_logic_state.current_focus;
add_logic_state.current_focus = match add_logic_state.current_focus {
AddLogicFocus::InputLogicName => AddLogicFocus::CancelButton, // Cycle back
AddLogicFocus::InputTargetColumn => AddLogicFocus::InputLogicName,
AddLogicFocus::InputDescription => AddLogicFocus::InputTargetColumn,
AddLogicFocus::InputScriptContent => AddLogicFocus::InputDescription,
AddLogicFocus::SaveButton => AddLogicFocus::InputScriptContent,
AddLogicFocus::CancelButton => AddLogicFocus::SaveButton,
};
// Update canvas field index if moving within canvas inputs
if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) {
let new_field = match add_logic_state.current_focus {
AddLogicFocus::InputLogicName => 0,
AddLogicFocus::InputTargetColumn => 1,
AddLogicFocus::InputDescription => 2,
_ => add_logic_state.current_field(), // Should not happen
};
add_logic_state.set_current_field(new_field);
}
app_state.ui.focus_outside_canvas = !matches!(
add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription
);
*command_message = format!("Focus: {:?}", add_logic_state.current_focus);
*is_edit_mode = 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::SaveButton => {
if let Some(table_def_id) = add_logic_state.selected_table_id {
if add_logic_state.target_column_input.trim().is_empty() {
*command_message = "Cannot save: Target Column cannot be empty.".to_string();
} else if add_logic_state.script_content_input.trim().is_empty() {
*command_message = "Cannot save: Script Content cannot be empty.".to_string();
} else {
*command_message = "Saving logic script...".to_string();
app_state.show_loading_dialog("Saving Script", "Please wait...");
let request = PostTableScriptRequest {
table_definition_id: table_def_id,
target_column: add_logic_state.target_column_input.trim().to_string(),
script: add_logic_state.script_content_input.trim().to_string(),
description: add_logic_state.description_input.trim().to_string(),
};
let mut client_clone = grpc_client.clone();
let sender_clone = save_logic_sender.clone();
tokio::spawn(async move {
let result = client_clone.post_table_script(request).await
.map(|res| format!("Script saved with ID: {}", res.id))
.map_err(|e| anyhow::anyhow!("gRPC call failed: {}", e));
let _ = sender_clone.send(result).await;
});
}
} else {
*command_message = "Cannot save: Table Definition ID is missing.".to_string();
}
handled = true;
}
AddLogicFocus::CancelButton => {
buffer_state.update_history(AppView::Admin); // Fixed: was AdminPanel
app_state.ui.focus_outside_canvas = true;
*command_message = "Cancelled Add Logic".to_string();
handled = true;
}
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn |
AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent => {
if !*is_edit_mode {
*is_edit_mode = true;
*command_message = "Edit mode: ON".to_string();
}
handled = true;
}
} }
} }
Some("toggle_edit_mode") => { handled = true;
*is_edit_mode = !*is_edit_mode;
*command_message = format!("Edit mode: {}", if *is_edit_mode { "ON" } else { "OFF" });
handled = true;
}
// Handle script content scrolling when not in edit mode
_ if !*is_edit_mode && add_logic_state.current_focus == AddLogicFocus::InputScriptContent => {
match action.as_deref() {
Some("move_up") => {
add_logic_state.script_content_scroll.0 = add_logic_state.script_content_scroll.0.saturating_sub(1);
handled = true;
}
Some("move_down") => {
add_logic_state.script_content_scroll.0 = add_logic_state.script_content_scroll.0.saturating_add(1);
handled = true;
}
_ => {}
}
}
_ => {}
} }
_ => {}
} }
handled handled
} }

View File

@@ -1,5 +1,10 @@
// src/state/pages/add_logic.rs // src/state/pages/add_logic.rs
use crate::state::pages::canvas_state::CanvasState; use crate::state::pages::canvas_state::CanvasState;
use std::cell::RefCell;
use std::rc::Rc;
use tui_textarea::TextArea;
// Removed unused Style, Color imports if not used in Default for TextArea styling
// use ratatui::style::{Color, Style}; // Keep if you add custom styling in default()
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AddLogicFocus { pub enum AddLogicFocus {
@@ -12,46 +17,47 @@ pub enum AddLogicFocus {
CancelButton, CancelButton,
} }
#[derive(Debug, Clone)] #[derive(Clone, Debug)]
pub struct AddLogicState { pub struct AddLogicState {
pub profile_name: String, pub profile_name: String,
pub selected_table_id: Option<i64>, pub selected_table_id: Option<i64>,
pub selected_table_name: Option<String>, pub selected_table_name: Option<String>,
pub logic_name_input: String, pub logic_name_input: String,
pub target_column_input: String, pub target_column_input: String,
pub script_content_input: String, pub script_content_editor: Rc<RefCell<TextArea<'static>>>,
pub description_input: String, pub description_input: String,
pub current_focus: AddLogicFocus, pub current_focus: AddLogicFocus,
pub logic_name_cursor_pos: usize, pub logic_name_cursor_pos: usize,
pub target_column_cursor_pos: usize, pub target_column_cursor_pos: usize,
pub script_content_scroll: (u16, u16), // (vertical, horizontal)
pub description_cursor_pos: usize, pub description_cursor_pos: usize,
pub has_unsaved_changes: bool, pub has_unsaved_changes: bool,
} }
impl Default for AddLogicState { impl Default for AddLogicState {
fn default() -> Self { fn default() -> Self {
let editor = TextArea::default(); // No 'mut' needed if not modified further here
// Example: editor.set_placeholder_text("Enter script...");
// Example: editor.set_line_number_style(Style::default().fg(Color::DarkGray));
AddLogicState { AddLogicState {
profile_name: "default".to_string(), profile_name: "default".to_string(),
selected_table_id: None, selected_table_id: None,
selected_table_name: None, selected_table_name: None,
logic_name_input: String::new(), logic_name_input: String::new(),
target_column_input: String::new(), target_column_input: String::new(),
script_content_input: String::new(), script_content_editor: Rc::new(RefCell::new(editor)),
description_input: String::new(), description_input: String::new(),
current_focus: AddLogicFocus::InputLogicName, current_focus: AddLogicFocus::InputLogicName,
logic_name_cursor_pos: 0, logic_name_cursor_pos: 0,
target_column_cursor_pos: 0, target_column_cursor_pos: 0,
script_content_scroll: (0, 0),
description_cursor_pos: 0, description_cursor_pos: 0,
has_unsaved_changes: false, has_unsaved_changes: false,
} }
} }
} }
// ... rest of the CanvasState impl remains the same
impl AddLogicState { impl AddLogicState {
// Number of canvas-editable fields pub const INPUT_FIELD_COUNT: usize = 3;
pub const INPUT_FIELD_COUNT: usize = 3; // Logic Name, Target Column, Description
} }
impl CanvasState for AddLogicState { impl CanvasState for AddLogicState {
@@ -60,7 +66,7 @@ impl CanvasState for AddLogicState {
AddLogicFocus::InputLogicName => 0, AddLogicFocus::InputLogicName => 0,
AddLogicFocus::InputTargetColumn => 1, AddLogicFocus::InputTargetColumn => 1,
AddLogicFocus::InputDescription => 2, AddLogicFocus::InputDescription => 2,
_ => 0, // Default or non-input focus _ => 0,
} }
} }
@@ -99,7 +105,7 @@ impl CanvasState for AddLogicState {
AddLogicFocus::InputLogicName => &mut self.logic_name_input, AddLogicFocus::InputLogicName => &mut self.logic_name_input,
AddLogicFocus::InputTargetColumn => &mut self.target_column_input, AddLogicFocus::InputTargetColumn => &mut self.target_column_input,
AddLogicFocus::InputDescription => &mut self.description_input, AddLogicFocus::InputDescription => &mut self.description_input,
_ => &mut self.logic_name_input, // Placeholder, should not be hit if focus is correct _ => &mut self.logic_name_input,
} }
} }
@@ -112,7 +118,7 @@ impl CanvasState for AddLogicState {
0 => AddLogicFocus::InputLogicName, 0 => AddLogicFocus::InputLogicName,
1 => AddLogicFocus::InputTargetColumn, 1 => AddLogicFocus::InputTargetColumn,
2 => AddLogicFocus::InputDescription, 2 => AddLogicFocus::InputDescription,
_ => self.current_focus, // Stay if out of bounds _ => self.current_focus,
}; };
} }
@@ -122,10 +128,12 @@ impl CanvasState for AddLogicState {
self.logic_name_cursor_pos = pos.min(self.logic_name_input.len()); self.logic_name_cursor_pos = pos.min(self.logic_name_input.len());
} }
AddLogicFocus::InputTargetColumn => { AddLogicFocus::InputTargetColumn => {
self.target_column_cursor_pos = pos.min(self.target_column_input.len()); self.target_column_cursor_pos =
pos.min(self.target_column_input.len());
} }
AddLogicFocus::InputDescription => { AddLogicFocus::InputDescription => {
self.description_cursor_pos = pos.min(self.description_input.len()); self.description_cursor_pos =
pos.min(self.description_input.len());
} }
_ => {} _ => {}
} }