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",
"async-trait",
"common",
"crossterm 0.29.0",
"crossterm",
"dirs 6.0.0",
"dotenvy",
"lazy_static",
@@ -485,15 +485,6 @@ version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "core-foundation"
version = "0.9.4"
@@ -621,24 +612,6 @@ dependencies = [
"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]]
name = "crossterm_winapi"
version = "0.9.1"
@@ -727,27 +700,6 @@ dependencies = [
"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]]
name = "digest"
version = "0.10.7"
@@ -813,15 +765,6 @@ dependencies = [
"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]]
name = "dotenvy"
version = "0.15.7"
@@ -1682,12 +1625,6 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
[[package]]
name = "litrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]]
name = "lock_api"
version = "0.4.12"
@@ -2355,7 +2292,7 @@ dependencies = [
"bitflags",
"cassowary",
"compact_str",
"crossterm 0.28.1",
"crossterm",
"indoc",
"instability",
"itertools 0.13.0",
@@ -3613,7 +3550,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae"
dependencies = [
"crossterm 0.28.1",
"crossterm",
"ratatui",
"unicode-width 0.2.0",
]

View File

@@ -9,12 +9,12 @@ anyhow = "1.0.98"
async-trait = "0.1.88"
common = { path = "../common" }
crossterm = "0.29.0"
crossterm = "0.28.1"
dirs = "6.0.0"
dotenvy = "0.15.7"
lazy_static = "1.5.0"
prost = "0.13.5"
ratatui = "0.29.0"
ratatui = { version = "0.29.0", features = ["crossterm"] }
serde = { version = "1.0.219", features = ["derive"] }
time = "0.3.41"
tokio = { version = "1.44.2", features = ["full", "macros"] }
@@ -22,6 +22,6 @@ toml = "0.8.20"
tonic = "0.13.0"
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
tui-textarea = "0.7.0"
tui-textarea = { version = "0.7.0", features = ["crossterm"] }
unicode-segmentation = "1.12.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 ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Modifier, Style},
text::{Line, Span, Text},
widgets::{Block, BorderType, Borders, Paragraph},
style::{Modifier, Style, Stylize}, // Added Stylize for .dim()
text::{Line, Span},
widgets::{Block, BorderType, Borders, Paragraph}, // Removed unused Widget
Frame,
};
use crate::components::handlers::canvas::render_canvas;
@@ -33,39 +33,43 @@ pub fn render_add_logic(
let inner_area = main_block.inner(area);
f.render_widget(main_block, area);
// --- Fullscreen Script Content Check ---
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()
.title(Span::styled(
" Steel Script Content (Fullscreen) ",
theme.fg,
)) // Indicate fullscreen
.title_alignment(Alignment::Center)
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(script_block_border_style);
editor.set_cursor_line_style(
Style::default().bg(theme.secondary),
);
editor.set_line_number_style(
Style::default().fg(theme.secondary),
);
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, inner_area); // Use inner_area for fullscreen
return; // IMPORTANT: Stop rendering here for fullscreen mode
editor.set_block(
Block::default()
.title(Span::styled(
" Steel Script Content (Ctrl+E or Enter to edit, Esc to unfocus/exit edit) ",
Style::default().fg(theme.fg),
))
.title_alignment(Alignment::Center)
.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()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3), // Top Info (Profile/Table)
Constraint::Length(9), // Canvas Area (3 input fields × 3 lines each)
Constraint::Min(5), // Script Content Area
Constraint::Length(3), // Bottom Buttons
Constraint::Length(3),
Constraint::Length(9),
Constraint::Min(5),
Constraint::Length(3),
])
.split(inner_area);
@@ -74,21 +78,22 @@ pub fn render_add_logic(
let script_content_area = main_chunks[2];
let buttons_area = main_chunks[3];
// Top Info Rendering
let profile_text = Paragraph::new(vec![
Line::from(Span::styled(
format!("Profile: {}", add_logic_state.profile_name),
theme.fg,
Style::default().fg(theme.fg),
)),
Line::from(Span::styled(
format!(
"Table: {}",
add_logic_state
.selected_table_id
.map(|id| format!("ID {}", id))
.unwrap_or_else(|| "Global".to_string())
.selected_table_name
.clone()
.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(
@@ -98,7 +103,6 @@ pub fn render_add_logic(
);
f.render_widget(profile_text, top_info_area);
// Canvas rendering for input fields
let focus_on_canvas_inputs = matches!(
add_logic_state.current_focus,
AddLogicFocus::InputLogicName
@@ -118,29 +122,23 @@ pub fn render_add_logic(
highlight_state,
);
// Script Content Area (Normal Mode)
let script_block_border_style =
if add_logic_state.current_focus == AddLogicFocus::InputScriptContent {
Style::default().fg(theme.highlight)
} else {
Style::default().fg(theme.secondary)
};
{
let mut editor = add_logic_state.script_content_editor.borrow_mut();
editor.set_cursor_line_style(Style::default());
editor.set_line_number_style(
Style::default().fg(theme.secondary).dim(), // Fixed: apply .dim() to Style, not Color
);
let script_block = Block::default()
.title(" Steel Script Content ")
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(script_block_border_style);
editor.set_block(
Block::default()
.title(" Steel Script Content ")
.borders(Borders::ALL)
.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 is_focused = current_focus == button_focus;
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()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(50), // Save Button
Constraint::Percentage(50), // Cancel Button
Constraint::Percentage(50),
Constraint::Percentage(50),
])
.split(buttons_area);
@@ -206,7 +203,6 @@ pub fn render_add_logic(
);
f.render_widget(cancel_button, button_chunks[1]);
// Dialog rendering
if app_state.ui.dialog.dialog_show {
dialog::render_dialog(
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::state::{
app::state::AppState,
@@ -6,17 +6,19 @@ use crate::state::{
app::buffer::AppView,
app::buffer::BufferState,
};
use crate::state::pages::canvas_state::CanvasState;
use crossterm::event::{KeyEvent};
// Now that client/Cargo.toml uses crossterm 0.28.1,
// this KeyEvent will match what ratatui and tui-textarea expect.
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 common::proto::multieko2::table_script::PostTableScriptRequest;
use tui_textarea::Input as TextAreaInput; // Import with an alias
pub type SaveLogicResultSender = mpsc::Sender<Result<String>>;
pub fn handle_add_logic_navigation(
key: KeyEvent,
key_event: KeyEvent, // This is crossterm::event::KeyEvent v0.28.1
config: &Config,
app_state: &mut AppState,
add_logic_state: &mut AddLogicState,
@@ -26,250 +28,180 @@ pub fn handle_add_logic_navigation(
save_logic_sender: SaveLogicResultSender,
command_message: &mut String,
) -> 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;
// Check if focus is on canvas input fields
let focus_on_canvas_inputs = matches!(
add_logic_state.current_focus,
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription
);
if add_logic_state.current_focus == AddLogicFocus::InputScriptContent {
// Add explicit type annotation for .into()
let textarea_input: TextAreaInput = key_event.into();
// Handle script content editing separately (multiline)
if *is_edit_mode && add_logic_state.current_focus == AddLogicFocus::InputScriptContent {
match key.code {
crossterm::event::KeyCode::Char(c) => {
add_logic_state.script_content_input.push(c);
add_logic_state.has_unsaved_changes = true;
handled = true;
if *is_edit_mode {
if key_event.code == KeyCode::Esc && key_event.modifiers == KeyModifiers::NONE {
*is_edit_mode = false;
*command_message = "Exited script edit mode. Press Ctrl+E/Enter to re-enter.".to_string();
return 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.script_content_scroll.0 = add_logic_state.script_content_scroll.0.saturating_add(1);
handled = true;
}
crossterm::event::KeyCode::Backspace => {
if !add_logic_state.script_content_input.is_empty() {
add_logic_state.script_content_input.pop();
add_logic_state.has_unsaved_changes = true;
handled = true;
} else {
match key_event.code {
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;
}
}
_ => {}
}
}
if !handled {
match action.as_deref() {
Some("exit_view") | Some("cancel_action") => {
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);
}
Some("toggle_edit_mode") => {
match add_logic_state.current_focus {
AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent => {
*is_edit_mode = !*is_edit_mode;
*command_message = format!("Edit mode: {}", if *is_edit_mode { "ON" } else { "OFF" });
}
// 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("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;
}
_ => {
*command_message = "Cannot toggle edit mode here.".to_string();
}
}
Some("toggle_edit_mode") => {
*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 = true;
}
_ => {}
}
handled
}

View File

@@ -1,5 +1,10 @@
// src/state/pages/add_logic.rs
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)]
pub enum AddLogicFocus {
@@ -12,46 +17,47 @@ pub enum AddLogicFocus {
CancelButton,
}
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
pub struct AddLogicState {
pub profile_name: String,
pub selected_table_id: Option<i64>,
pub selected_table_name: Option<String>,
pub logic_name_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 current_focus: AddLogicFocus,
pub logic_name_cursor_pos: usize,
pub target_column_cursor_pos: usize,
pub script_content_scroll: (u16, u16), // (vertical, horizontal)
pub description_cursor_pos: usize,
pub has_unsaved_changes: bool,
}
impl Default for AddLogicState {
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 {
profile_name: "default".to_string(),
selected_table_id: None,
selected_table_name: None,
logic_name_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(),
current_focus: AddLogicFocus::InputLogicName,
logic_name_cursor_pos: 0,
target_column_cursor_pos: 0,
script_content_scroll: (0, 0),
description_cursor_pos: 0,
has_unsaved_changes: false,
}
}
}
// ... rest of the CanvasState impl remains the same
impl AddLogicState {
// Number of canvas-editable fields
pub const INPUT_FIELD_COUNT: usize = 3; // Logic Name, Target Column, Description
pub const INPUT_FIELD_COUNT: usize = 3;
}
impl CanvasState for AddLogicState {
@@ -60,7 +66,7 @@ impl CanvasState for AddLogicState {
AddLogicFocus::InputLogicName => 0,
AddLogicFocus::InputTargetColumn => 1,
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::InputTargetColumn => &mut self.target_column_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,
1 => AddLogicFocus::InputTargetColumn,
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());
}
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 => {
self.description_cursor_pos = pos.min(self.description_input.len());
self.description_cursor_pos =
pos.min(self.description_input.len());
}
_ => {}
}