add logic is now using canvas library now
This commit is contained in:
@@ -321,6 +321,18 @@ impl EventHandler {
|
||||
if !outcome.get_message_if_ok().is_empty() {
|
||||
return Ok(outcome);
|
||||
}
|
||||
} else if let Page::AddLogic(add_logic_page) = &mut router.current {
|
||||
let outcome = add_logic::event::handle_add_logic_event(
|
||||
event,
|
||||
config,
|
||||
app_state,
|
||||
add_logic_page,
|
||||
self.grpc_client.clone(),
|
||||
self.save_logic_result_sender.clone(),
|
||||
)?;
|
||||
if !outcome.get_message_if_ok().is_empty() {
|
||||
return Ok(outcome);
|
||||
}
|
||||
} else if let Page::Form(path) = &router.current {
|
||||
let outcome = forms::event::handle_form_event(
|
||||
event,
|
||||
@@ -469,24 +481,6 @@ impl EventHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Optional page-specific handlers (non-movement or rich actions)
|
||||
let client_clone = self.grpc_client.clone();
|
||||
let sender_clone = self.save_logic_result_sender.clone();
|
||||
if add_logic::nav::handle_add_logic_navigation(
|
||||
key_event,
|
||||
config,
|
||||
app_state,
|
||||
buffer_state,
|
||||
client_clone,
|
||||
sender_clone,
|
||||
&mut self.command_message,
|
||||
router,
|
||||
) {
|
||||
return Ok(EventOutcome::Ok(
|
||||
self.command_message.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Page::AddTable(add_table_state) = &mut router.current {
|
||||
let client_clone = self.grpc_client.clone();
|
||||
let sender_clone = self.save_table_result_sender.clone();
|
||||
|
||||
199
client/src/pages/admin_panel/add_logic/event.rs
Normal file
199
client/src/pages/admin_panel/add_logic/event.rs
Normal file
@@ -0,0 +1,199 @@
|
||||
// src/pages/admin_panel/add_logic/event.rs
|
||||
use anyhow::Result;
|
||||
use crossterm::event::{Event, KeyCode, KeyModifiers};
|
||||
use crate::config::binds::config::Config;
|
||||
use crate::modes::handlers::event::EventOutcome;
|
||||
use crate::pages::admin_panel::add_logic::state::{AddLogicFormState, AddLogicFocus};
|
||||
use crate::components::common::text_editor::TextEditor;
|
||||
use crate::services::grpc_client::GrpcClient;
|
||||
use crate::pages::admin_panel::add_logic::nav::SaveLogicResultSender; // keep type alias
|
||||
use crate::state::app::state::AppState;
|
||||
use canvas::DataProvider;
|
||||
|
||||
pub fn handle_add_logic_event(
|
||||
event: Event,
|
||||
config: &Config,
|
||||
app_state: &mut AppState,
|
||||
add_logic_page: &mut AddLogicFormState,
|
||||
grpc_client: GrpcClient,
|
||||
save_logic_sender: SaveLogicResultSender,
|
||||
) -> Result<EventOutcome> {
|
||||
if let Event::Key(key_event) = event {
|
||||
let st = &mut add_logic_page.state;
|
||||
let key_code = key_event.code;
|
||||
let modifiers = key_event.modifiers;
|
||||
|
||||
// 1) Fullscreen Script Editor mode
|
||||
if st.current_focus == AddLogicFocus::InsideScriptContent {
|
||||
match key_code {
|
||||
KeyCode::Esc if modifiers == KeyModifiers::NONE => {
|
||||
st.current_focus = AddLogicFocus::ScriptContentPreview;
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
return Ok(EventOutcome::Ok("Exited script editing.".into()));
|
||||
}
|
||||
_ => {
|
||||
let changed = {
|
||||
let mut editor_borrow = st.script_content_editor.borrow_mut();
|
||||
TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
key_event,
|
||||
&st.editor_keybinding_mode,
|
||||
&mut st.vim_state,
|
||||
)
|
||||
};
|
||||
if changed {
|
||||
st.has_unsaved_changes = true;
|
||||
return Ok(EventOutcome::Ok("Script updated".into()));
|
||||
} else {
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Canvas inputs (three fields) – forward to FormEditor
|
||||
let inside_canvas_inputs = matches!(
|
||||
st.current_focus,
|
||||
AddLogicFocus::InputLogicName
|
||||
| AddLogicFocus::InputTargetColumn
|
||||
| AddLogicFocus::InputDescription
|
||||
);
|
||||
|
||||
if inside_canvas_inputs {
|
||||
// Handoff from last field to Script Preview on "down"/"next"
|
||||
let last_idx = add_logic_page
|
||||
.editor
|
||||
.data_provider()
|
||||
.field_count()
|
||||
.saturating_sub(1);
|
||||
let at_last = add_logic_page.editor.current_field() >= last_idx;
|
||||
|
||||
// Map to generic "down/next" via config or raw keys
|
||||
let action_opt = config.get_general_action(key_code, modifiers);
|
||||
let is_down_or_next = matches!(
|
||||
action_opt,
|
||||
Some("down") | Some("next") | Some("move_down") | Some("next_field")
|
||||
)
|
||||
|| matches!(key_code, KeyCode::Down)
|
||||
|| matches!(key_code, KeyCode::Char('j') if modifiers.is_empty());
|
||||
|
||||
if at_last && is_down_or_next {
|
||||
st.last_canvas_field = last_idx;
|
||||
st.current_focus = AddLogicFocus::ScriptContentPreview;
|
||||
add_logic_page.focus_outside_canvas = true;
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
return Ok(EventOutcome::Ok(
|
||||
"Moved to Script Preview".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Normal canvas input
|
||||
match add_logic_page.editor.handle_key_event(key_event) {
|
||||
canvas::keymap::KeyEventOutcome::Consumed(Some(msg)) => {
|
||||
add_logic_page.sync_from_editor();
|
||||
return Ok(EventOutcome::Ok(msg));
|
||||
}
|
||||
canvas::keymap::KeyEventOutcome::Consumed(None) => {
|
||||
add_logic_page.sync_from_editor();
|
||||
return Ok(EventOutcome::Ok("Input updated".into()));
|
||||
}
|
||||
canvas::keymap::KeyEventOutcome::Pending => {
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
canvas::keymap::KeyEventOutcome::NotMatched => {
|
||||
// fall through to outside handling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Outside-canvas focus (Script Preview, Save, Cancel)
|
||||
let action_opt = config.get_general_action(key_code, modifiers);
|
||||
|
||||
match action_opt {
|
||||
Some("up") | Some("move_up") | Some("previous") | Some("previous_option") | Some("prev_field") => {
|
||||
match st.current_focus {
|
||||
AddLogicFocus::ScriptContentPreview => {
|
||||
st.current_focus = AddLogicFocus::InputDescription;
|
||||
add_logic_page.focus_outside_canvas = false;
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
return Ok(EventOutcome::Ok(
|
||||
"Back to Description".to_string(),
|
||||
));
|
||||
}
|
||||
AddLogicFocus::SaveButton => {
|
||||
st.current_focus = AddLogicFocus::ScriptContentPreview;
|
||||
return Ok(EventOutcome::Ok(
|
||||
"Back to Script Preview".to_string(),
|
||||
));
|
||||
}
|
||||
AddLogicFocus::CancelButton => {
|
||||
st.current_focus = AddLogicFocus::SaveButton;
|
||||
return Ok(EventOutcome::Ok("Back to Save".to_string()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Some("down") | Some("move_down") | Some("next") | Some("next_option") | Some("next_field") => {
|
||||
match st.current_focus {
|
||||
AddLogicFocus::ScriptContentPreview => {
|
||||
st.current_focus = AddLogicFocus::SaveButton;
|
||||
return Ok(EventOutcome::Ok("Focus: Save".to_string()));
|
||||
}
|
||||
AddLogicFocus::SaveButton => {
|
||||
st.current_focus = AddLogicFocus::CancelButton;
|
||||
return Ok(EventOutcome::Ok("Focus: Cancel".to_string()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Some("select") => {
|
||||
match st.current_focus {
|
||||
AddLogicFocus::ScriptContentPreview => {
|
||||
st.current_focus = AddLogicFocus::InsideScriptContent;
|
||||
add_logic_page.focus_outside_canvas = false;
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
return Ok(EventOutcome::Ok(
|
||||
"Fullscreen script editing. Esc to exit.".into(),
|
||||
));
|
||||
}
|
||||
AddLogicFocus::SaveButton => {
|
||||
if let Some(msg) = st.save_logic() {
|
||||
return Ok(EventOutcome::Ok(msg));
|
||||
}
|
||||
return Ok(EventOutcome::Ok(
|
||||
"Saved (no changes)".to_string(),
|
||||
));
|
||||
}
|
||||
AddLogicFocus::CancelButton => {
|
||||
// Keep this simple: you can wire buffer/view navigation where needed
|
||||
return Ok(EventOutcome::Ok(
|
||||
"Cancelled Add Logic".to_string(),
|
||||
));
|
||||
}
|
||||
AddLogicFocus::InputLogicName
|
||||
| AddLogicFocus::InputTargetColumn
|
||||
| AddLogicFocus::InputDescription => {
|
||||
add_logic_page.focus_outside_canvas = false;
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Some("esc") => {
|
||||
if st.current_focus == AddLogicFocus::ScriptContentPreview {
|
||||
// Go back to Description (last canvas field)
|
||||
st.current_focus = AddLogicFocus::InputDescription;
|
||||
add_logic_page.focus_outside_canvas = false;
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
return Ok(EventOutcome::Ok(
|
||||
"Back to Description".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(EventOutcome::Ok(String::new()))
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
use anyhow::{Context, Result};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::pages::admin_panel::add_logic::state::AddLogicState;
|
||||
use crate::pages::admin_panel::add_logic::state::AddLogicFormState;
|
||||
use crate::pages::routing::{Page, Router};
|
||||
use crate::services::grpc_client::GrpcClient;
|
||||
use crate::services::ui_service::UiService;
|
||||
@@ -19,9 +19,9 @@ pub async fn process_pending_table_structure_fetch(
|
||||
let mut needs_redraw = false;
|
||||
|
||||
if let Some((profile_name, table_name)) = app_state.pending_table_structure_fetch.take() {
|
||||
if let Page::AddLogic(state) = &mut router.current {
|
||||
if state.profile_name == profile_name
|
||||
&& state.selected_table_name.as_deref() == Some(table_name.as_str())
|
||||
if let Page::AddLogic(page) = &mut router.current {
|
||||
if page.profile_name() == profile_name
|
||||
&& page.selected_table_name().map(|s| s.as_str()) == Some(table_name.as_str())
|
||||
{
|
||||
info!(
|
||||
"Fetching table structure for {}.{}",
|
||||
@@ -30,7 +30,7 @@ pub async fn process_pending_table_structure_fetch(
|
||||
|
||||
let fetch_message = UiService::initialize_add_logic_table_data(
|
||||
grpc_client,
|
||||
state,
|
||||
&mut page.state, // keep state here, UiService expects AddLogicState
|
||||
&app_state.profile_tree,
|
||||
)
|
||||
.await
|
||||
@@ -53,7 +53,10 @@ pub async fn process_pending_table_structure_fetch(
|
||||
error!(
|
||||
"Mismatch in pending_table_structure_fetch: app_state wants {}.{}, \
|
||||
but AddLogic state is for {}.{:?}",
|
||||
profile_name, table_name, state.profile_name, state.selected_table_name
|
||||
profile_name,
|
||||
table_name,
|
||||
page.profile_name(),
|
||||
page.selected_table_name()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -71,14 +74,15 @@ pub async fn process_pending_table_structure_fetch(
|
||||
/// fetch them and update the state. Returns true if UI needs a redraw.
|
||||
pub async fn maybe_fetch_columns_for_awaiting_table(
|
||||
grpc_client: &mut GrpcClient,
|
||||
state: &mut AddLogicState,
|
||||
page: &mut AddLogicFormState,
|
||||
command_message: &mut String,
|
||||
) -> Result<bool> {
|
||||
if let Some(table_name) = state
|
||||
if let Some(table_name) = page
|
||||
.state
|
||||
.script_editor_awaiting_column_autocomplete
|
||||
.clone()
|
||||
{
|
||||
let profile_name = state.profile_name.clone();
|
||||
let profile_name = page.state.profile_name.clone();
|
||||
|
||||
info!(
|
||||
"Fetching columns for table selection: {}.{}",
|
||||
@@ -86,7 +90,7 @@ pub async fn maybe_fetch_columns_for_awaiting_table(
|
||||
);
|
||||
match UiService::fetch_columns_for_table(grpc_client, &profile_name, &table_name).await {
|
||||
Ok(columns) => {
|
||||
state.set_columns_for_table_autocomplete(columns.clone());
|
||||
page.state.set_columns_for_table_autocomplete(columns.clone());
|
||||
info!("Loaded {} columns for table '{}'", columns.len(), table_name);
|
||||
*command_message =
|
||||
format!("Columns for '{}' loaded. Select a column.", table_name);
|
||||
@@ -96,8 +100,8 @@ pub async fn maybe_fetch_columns_for_awaiting_table(
|
||||
"Failed to fetch columns for {}.{}: {}",
|
||||
profile_name, table_name, e
|
||||
);
|
||||
state.script_editor_awaiting_column_autocomplete = None;
|
||||
state.deactivate_script_editor_autocomplete();
|
||||
page.state.script_editor_awaiting_column_autocomplete = None;
|
||||
page.state.deactivate_script_editor_autocomplete();
|
||||
*command_message = format!("Error loading columns for '{}': {}", table_name, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,3 +4,4 @@ pub mod ui;
|
||||
pub mod nav;
|
||||
pub mod state;
|
||||
pub mod loader;
|
||||
pub mod event;
|
||||
|
||||
@@ -1,531 +1,6 @@
|
||||
// src/pages/admin_panel/add_logic/nav.rs
|
||||
|
||||
use crate::config::binds::config::{Config, EditorKeybindingMode};
|
||||
use crate::state::app::state::AppState;
|
||||
use crate::pages::admin_panel::add_logic::state::{AddLogicFocus, AddLogicState};
|
||||
use crate::buffer::{AppView, BufferState};
|
||||
use crossterm::event::{KeyEvent, KeyCode, KeyModifiers};
|
||||
use crate::services::GrpcClient;
|
||||
use tokio::sync::mpsc;
|
||||
use anyhow::Result;
|
||||
use crate::components::common::text_editor::TextEditor;
|
||||
use crate::services::ui_service::UiService;
|
||||
use tui_textarea::CursorMove;
|
||||
use crate::pages::admin::AdminState;
|
||||
use crate::pages::routing::{Router, Page};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub type SaveLogicResultSender = mpsc::Sender<Result<String>>;
|
||||
|
||||
pub fn handle_add_logic_navigation(
|
||||
key_event: KeyEvent,
|
||||
config: &Config,
|
||||
app_state: &mut AppState,
|
||||
buffer_state: &mut BufferState,
|
||||
grpc_client: GrpcClient,
|
||||
save_logic_sender: SaveLogicResultSender,
|
||||
command_message: &mut String,
|
||||
router: &mut Router,
|
||||
) -> bool {
|
||||
if let Page::AddLogic(add_logic_state) = &mut router.current {
|
||||
// === FULLSCREEN SCRIPT EDITING ===
|
||||
if add_logic_state.current_focus == AddLogicFocus::InsideScriptContent {
|
||||
// === AUTOCOMPLETE HANDLING ===
|
||||
if add_logic_state.script_editor_autocomplete_active {
|
||||
match key_event.code {
|
||||
KeyCode::Char(c) if c.is_alphanumeric() || c == '_' => {
|
||||
add_logic_state.script_editor_filter_text.push(c);
|
||||
add_logic_state.update_script_editor_suggestions();
|
||||
{
|
||||
let mut editor_borrow =
|
||||
add_logic_state.script_content_editor.borrow_mut();
|
||||
TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
key_event,
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state,
|
||||
);
|
||||
}
|
||||
*command_message =
|
||||
format!("Filtering: @{}", add_logic_state.script_editor_filter_text);
|
||||
return true;
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
if !add_logic_state.script_editor_filter_text.is_empty() {
|
||||
add_logic_state.script_editor_filter_text.pop();
|
||||
add_logic_state.update_script_editor_suggestions();
|
||||
{
|
||||
let mut editor_borrow =
|
||||
add_logic_state.script_content_editor.borrow_mut();
|
||||
TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
key_event,
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state,
|
||||
);
|
||||
}
|
||||
*command_message =
|
||||
if add_logic_state.script_editor_filter_text.is_empty() {
|
||||
"Autocomplete: @".to_string()
|
||||
} else {
|
||||
format!(
|
||||
"Filtering: @{}",
|
||||
add_logic_state.script_editor_filter_text
|
||||
)
|
||||
};
|
||||
} else {
|
||||
let should_deactivate =
|
||||
if let Some((trigger_line, trigger_col)) =
|
||||
add_logic_state.script_editor_trigger_position
|
||||
{
|
||||
let current_cursor = {
|
||||
let editor_borrow =
|
||||
add_logic_state.script_content_editor.borrow();
|
||||
editor_borrow.cursor()
|
||||
};
|
||||
current_cursor.0 == trigger_line
|
||||
&& current_cursor.1 == trigger_col + 1
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if should_deactivate {
|
||||
add_logic_state.deactivate_script_editor_autocomplete();
|
||||
*command_message = "Autocomplete cancelled".to_string();
|
||||
}
|
||||
{
|
||||
let mut editor_borrow =
|
||||
add_logic_state.script_content_editor.borrow_mut();
|
||||
TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
key_event,
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state,
|
||||
);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
KeyCode::Tab | KeyCode::Down => {
|
||||
if !add_logic_state.script_editor_suggestions.is_empty() {
|
||||
let current = add_logic_state
|
||||
.script_editor_selected_suggestion_index
|
||||
.unwrap_or(0);
|
||||
let next =
|
||||
(current + 1) % add_logic_state.script_editor_suggestions.len();
|
||||
add_logic_state.script_editor_selected_suggestion_index = Some(next);
|
||||
*command_message = format!(
|
||||
"Selected: {}",
|
||||
add_logic_state.script_editor_suggestions[next]
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
KeyCode::Up => {
|
||||
if !add_logic_state.script_editor_suggestions.is_empty() {
|
||||
let current = add_logic_state
|
||||
.script_editor_selected_suggestion_index
|
||||
.unwrap_or(0);
|
||||
let prev = if current == 0 {
|
||||
add_logic_state.script_editor_suggestions.len() - 1
|
||||
} else {
|
||||
current - 1
|
||||
};
|
||||
add_logic_state.script_editor_selected_suggestion_index = Some(prev);
|
||||
*command_message = format!(
|
||||
"Selected: {}",
|
||||
add_logic_state.script_editor_suggestions[prev]
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
if let Some(selected_idx) =
|
||||
add_logic_state.script_editor_selected_suggestion_index
|
||||
{
|
||||
if let Some(suggestion) = add_logic_state
|
||||
.script_editor_suggestions
|
||||
.get(selected_idx)
|
||||
.cloned()
|
||||
{
|
||||
let trigger_pos =
|
||||
add_logic_state.script_editor_trigger_position;
|
||||
let filter_len =
|
||||
add_logic_state.script_editor_filter_text.len();
|
||||
|
||||
add_logic_state.deactivate_script_editor_autocomplete();
|
||||
add_logic_state.has_unsaved_changes = true;
|
||||
|
||||
if let Some(pos) = trigger_pos {
|
||||
let mut editor_borrow =
|
||||
add_logic_state.script_content_editor.borrow_mut();
|
||||
|
||||
if suggestion == "sql" {
|
||||
replace_autocomplete_text(
|
||||
&mut editor_borrow,
|
||||
pos,
|
||||
filter_len,
|
||||
"sql",
|
||||
);
|
||||
editor_borrow.insert_str("('')");
|
||||
editor_borrow.move_cursor(CursorMove::Back);
|
||||
editor_borrow.move_cursor(CursorMove::Back);
|
||||
*command_message = "Inserted: @sql('')".to_string();
|
||||
} else {
|
||||
let is_table_selection =
|
||||
add_logic_state.is_table_name_suggestion(&suggestion);
|
||||
replace_autocomplete_text(
|
||||
&mut editor_borrow,
|
||||
pos,
|
||||
filter_len,
|
||||
&suggestion,
|
||||
);
|
||||
|
||||
if is_table_selection {
|
||||
editor_borrow.insert_str(".");
|
||||
let new_cursor = editor_borrow.cursor();
|
||||
drop(editor_borrow);
|
||||
|
||||
add_logic_state.script_editor_trigger_position =
|
||||
Some(new_cursor);
|
||||
add_logic_state.script_editor_autocomplete_active = true;
|
||||
add_logic_state.script_editor_filter_text.clear();
|
||||
add_logic_state
|
||||
.trigger_column_autocomplete_for_table(
|
||||
suggestion.clone(),
|
||||
);
|
||||
|
||||
let profile_name =
|
||||
add_logic_state.profile_name.clone();
|
||||
let table_name_for_fetch = suggestion.clone();
|
||||
let mut client_clone = grpc_client.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = UiService::fetch_columns_for_table(
|
||||
&mut client_clone,
|
||||
&profile_name,
|
||||
&table_name_for_fetch,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to fetch columns for {}.{}: {}",
|
||||
profile_name,
|
||||
table_name_for_fetch,
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
*command_message = format!(
|
||||
"Selected table '{}', fetching columns...",
|
||||
suggestion
|
||||
);
|
||||
} else {
|
||||
*command_message =
|
||||
format!("Inserted: {}", suggestion);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
add_logic_state.deactivate_script_editor_autocomplete();
|
||||
{
|
||||
let mut editor_borrow =
|
||||
add_logic_state.script_content_editor.borrow_mut();
|
||||
TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
key_event,
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
add_logic_state.deactivate_script_editor_autocomplete();
|
||||
*command_message = "Autocomplete cancelled".to_string();
|
||||
}
|
||||
_ => {
|
||||
add_logic_state.deactivate_script_editor_autocomplete();
|
||||
*command_message = "Autocomplete cancelled".to_string();
|
||||
{
|
||||
let mut editor_borrow =
|
||||
add_logic_state.script_content_editor.borrow_mut();
|
||||
TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
key_event,
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger autocomplete with '@'
|
||||
if key_event.code == KeyCode::Char('@') && key_event.modifiers == KeyModifiers::NONE {
|
||||
let should_trigger = match add_logic_state.editor_keybinding_mode {
|
||||
EditorKeybindingMode::Vim => {
|
||||
TextEditor::is_vim_insert_mode(&add_logic_state.vim_state)
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
if should_trigger {
|
||||
let cursor_before = {
|
||||
let editor_borrow = add_logic_state.script_content_editor.borrow();
|
||||
editor_borrow.cursor()
|
||||
};
|
||||
{
|
||||
let mut editor_borrow =
|
||||
add_logic_state.script_content_editor.borrow_mut();
|
||||
TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
key_event,
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state,
|
||||
);
|
||||
}
|
||||
add_logic_state.script_editor_trigger_position = Some(cursor_before);
|
||||
add_logic_state.script_editor_autocomplete_active = true;
|
||||
add_logic_state.script_editor_filter_text.clear();
|
||||
add_logic_state.update_script_editor_suggestions();
|
||||
add_logic_state.has_unsaved_changes = true;
|
||||
*command_message = "Autocomplete: @ (Tab/↑↓ to navigate, Enter to select, Esc to cancel)".to_string();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Esc handling
|
||||
if key_event.code == KeyCode::Esc && key_event.modifiers == KeyModifiers::NONE {
|
||||
match add_logic_state.editor_keybinding_mode {
|
||||
EditorKeybindingMode::Vim => {
|
||||
let was_insert =
|
||||
TextEditor::is_vim_insert_mode(&add_logic_state.vim_state);
|
||||
{
|
||||
let mut editor_borrow =
|
||||
add_logic_state.script_content_editor.borrow_mut();
|
||||
TextEditor::handle_input(
|
||||
&mut editor_borrow,
|
||||
key_event,
|
||||
&add_logic_state.editor_keybinding_mode,
|
||||
&mut add_logic_state.vim_state,
|
||||
);
|
||||
}
|
||||
if was_insert {
|
||||
*command_message =
|
||||
"VIM: Normal Mode. Esc again to exit script.".to_string();
|
||||
} else {
|
||||
add_logic_state.current_focus =
|
||||
AddLogicFocus::ScriptContentPreview;
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
*command_message = "Exited script editing.".to_string();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
add_logic_state.current_focus = AddLogicFocus::ScriptContentPreview;
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
*command_message = "Exited script editing.".to_string();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Normal text input
|
||||
let changed = {
|
||||
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// === NON-FULLSCREEN NAVIGATION ===
|
||||
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") => {
|
||||
handled = false;
|
||||
}
|
||||
Some("move_up") => {
|
||||
match current_focus {
|
||||
AddLogicFocus::InputLogicName => {}
|
||||
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 => {}
|
||||
_ => 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 => {}
|
||||
_ => handled = false,
|
||||
}
|
||||
}
|
||||
Some("previous_option") => {
|
||||
match current_focus {
|
||||
AddLogicFocus::InputLogicName
|
||||
| AddLogicFocus::InputTargetColumn
|
||||
| AddLogicFocus::InputDescription => {}
|
||||
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;
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
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
|
||||
);
|
||||
handled = true;
|
||||
}
|
||||
AddLogicFocus::SaveButton => {
|
||||
*command_message = "Save logic action".to_string();
|
||||
handled = true;
|
||||
}
|
||||
AddLogicFocus::CancelButton => {
|
||||
buffer_state.update_history(AppView::Admin);
|
||||
*command_message = "Cancelled Add Logic".to_string();
|
||||
handled = true;
|
||||
}
|
||||
AddLogicFocus::InputLogicName
|
||||
| AddLogicFocus::InputTargetColumn
|
||||
| AddLogicFocus::InputDescription => {
|
||||
// Focus canvas inputs; let canvas keymap handle editing
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
handled = false; // forward to canvas
|
||||
}
|
||||
_ => handled = false,
|
||||
}
|
||||
}
|
||||
Some("toggle_edit_mode") => {
|
||||
match current_focus {
|
||||
AddLogicFocus::InputLogicName
|
||||
| AddLogicFocus::InputTargetColumn
|
||||
| AddLogicFocus::InputDescription => {
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
*command_message =
|
||||
"Focus moved to input. Use i/a (Vim) or type to edit.".to_string();
|
||||
handled = true;
|
||||
}
|
||||
_ => {
|
||||
*command_message = "Cannot toggle edit mode here.".to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => handled = false,
|
||||
}
|
||||
|
||||
if handled && current_focus != new_focus {
|
||||
add_logic_state.current_focus = new_focus;
|
||||
let new_is_canvas_input_focus = matches!(
|
||||
new_focus,
|
||||
AddLogicFocus::InputLogicName
|
||||
| AddLogicFocus::InputTargetColumn
|
||||
| AddLogicFocus::InputDescription
|
||||
);
|
||||
if new_is_canvas_input_focus {
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
} else {
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
}
|
||||
}
|
||||
handled
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_autocomplete_text(
|
||||
editor: &mut tui_textarea::TextArea,
|
||||
trigger_pos: (usize, usize),
|
||||
filter_len: usize,
|
||||
replacement: &str,
|
||||
) {
|
||||
let filter_start_pos = (trigger_pos.0, trigger_pos.1 + 1);
|
||||
editor.move_cursor(CursorMove::Jump(filter_start_pos.0 as u16, filter_start_pos.1 as u16));
|
||||
for _ in 0..filter_len {
|
||||
editor.delete_next_char();
|
||||
}
|
||||
editor.insert_str(replacement);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/pages/admin_panel/add_logic/state.rs
|
||||
use crate::config::binds::config::{EditorConfig, EditorKeybindingMode};
|
||||
use crate::components::common::text_editor::{TextEditor, VimState};
|
||||
use canvas::{DataProvider, AppMode};
|
||||
use canvas::{DataProvider, AppMode, FormEditor};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use tui_textarea::TextArea;
|
||||
@@ -315,3 +315,141 @@ impl DataProvider for AddLogicState {
|
||||
field_index == 1
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper that owns both the raw state and its FormEditor (like LoginFormState)
|
||||
pub struct AddLogicFormState {
|
||||
pub state: AddLogicState,
|
||||
pub editor: FormEditor<AddLogicState>,
|
||||
pub focus_outside_canvas: bool,
|
||||
}
|
||||
|
||||
// manual Debug because FormEditor may not implement Debug
|
||||
impl std::fmt::Debug for AddLogicFormState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AddLogicFormState")
|
||||
.field("state", &self.state)
|
||||
.field("focus_outside_canvas", &self.focus_outside_canvas)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl AddLogicFormState {
|
||||
pub fn new(editor_config: &EditorConfig) -> Self {
|
||||
let state = AddLogicState::new(editor_config);
|
||||
let editor = FormEditor::new(state.clone());
|
||||
Self {
|
||||
state,
|
||||
editor,
|
||||
focus_outside_canvas: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_state(state: AddLogicState) -> Self {
|
||||
let editor = FormEditor::new(state.clone());
|
||||
Self {
|
||||
state,
|
||||
editor,
|
||||
focus_outside_canvas: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync state from editor's data provider snapshot
|
||||
pub fn sync_from_editor(&mut self) {
|
||||
self.state = self.editor.data_provider().clone();
|
||||
}
|
||||
|
||||
// === Delegates to AddLogicState fields ===
|
||||
|
||||
pub fn current_focus(&self) -> AddLogicFocus {
|
||||
self.state.current_focus
|
||||
}
|
||||
|
||||
pub fn set_current_focus(&mut self, focus: AddLogicFocus) {
|
||||
self.state.current_focus = focus;
|
||||
}
|
||||
|
||||
pub fn has_unsaved_changes(&self) -> bool {
|
||||
self.state.has_unsaved_changes
|
||||
}
|
||||
|
||||
pub fn set_has_unsaved_changes(&mut self, changed: bool) {
|
||||
self.state.has_unsaved_changes = changed;
|
||||
}
|
||||
|
||||
pub fn profile_name(&self) -> &str {
|
||||
&self.state.profile_name
|
||||
}
|
||||
|
||||
pub fn selected_table_name(&self) -> Option<&String> {
|
||||
self.state.selected_table_name.as_ref()
|
||||
}
|
||||
|
||||
pub fn selected_table_id(&self) -> Option<i64> {
|
||||
self.state.selected_table_id
|
||||
}
|
||||
|
||||
pub fn script_content_editor(&self) -> &Rc<RefCell<TextArea<'static>>> {
|
||||
&self.state.script_content_editor
|
||||
}
|
||||
|
||||
pub fn script_content_editor_mut(&mut self) -> &mut Rc<RefCell<TextArea<'static>>> {
|
||||
&mut self.state.script_content_editor
|
||||
}
|
||||
|
||||
pub fn vim_state(&self) -> &VimState {
|
||||
&self.state.vim_state
|
||||
}
|
||||
|
||||
pub fn vim_state_mut(&mut self) -> &mut VimState {
|
||||
&mut self.state.vim_state
|
||||
}
|
||||
|
||||
pub fn editor_keybinding_mode(&self) -> &EditorKeybindingMode {
|
||||
&self.state.editor_keybinding_mode
|
||||
}
|
||||
|
||||
pub fn script_editor_autocomplete_active(&self) -> bool {
|
||||
self.state.script_editor_autocomplete_active
|
||||
}
|
||||
|
||||
pub fn script_editor_suggestions(&self) -> &Vec<String> {
|
||||
&self.state.script_editor_suggestions
|
||||
}
|
||||
|
||||
pub fn script_editor_selected_suggestion_index(&self) -> Option<usize> {
|
||||
self.state.script_editor_selected_suggestion_index
|
||||
}
|
||||
|
||||
pub fn target_column_suggestions(&self) -> &Vec<String> {
|
||||
&self.state.target_column_suggestions
|
||||
}
|
||||
|
||||
pub fn selected_target_column_suggestion_index(&self) -> Option<usize> {
|
||||
self.state.selected_target_column_suggestion_index
|
||||
}
|
||||
|
||||
pub fn in_target_column_suggestion_mode(&self) -> bool {
|
||||
self.state.in_target_column_suggestion_mode
|
||||
}
|
||||
|
||||
pub fn show_target_column_suggestions(&self) -> bool {
|
||||
self.state.show_target_column_suggestions
|
||||
}
|
||||
|
||||
// === Delegates to FormEditor ===
|
||||
|
||||
pub fn mode(&self) -> AppMode {
|
||||
self.editor.mode()
|
||||
}
|
||||
|
||||
pub fn cursor_position(&self) -> usize {
|
||||
self.editor.cursor_position()
|
||||
}
|
||||
|
||||
pub fn handle_key_event(
|
||||
&mut self,
|
||||
key_event: crossterm::event::KeyEvent,
|
||||
) -> canvas::keymap::KeyEventOutcome {
|
||||
self.editor.handle_key_event(key_event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/pages/admin_panel/add_logic/ui.rs
|
||||
use crate::config::colors::themes::Theme;
|
||||
use crate::state::app::state::AppState;
|
||||
use crate::pages::admin_panel::add_logic::state::{AddLogicFocus, AddLogicState};
|
||||
use crate::pages::admin_panel::add_logic::state::{AddLogicFocus, AddLogicState, AddLogicFormState};
|
||||
use canvas::{render_canvas, FormEditor};
|
||||
use ratatui::{
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
@@ -19,7 +19,7 @@ pub fn render_add_logic(
|
||||
area: Rect,
|
||||
theme: &Theme,
|
||||
app_state: &AppState,
|
||||
add_logic_state: &mut AddLogicState,
|
||||
add_logic_state: &mut AddLogicFormState,
|
||||
) {
|
||||
let main_block = Block::default()
|
||||
.title(" Add New Logic Script ")
|
||||
@@ -32,9 +32,13 @@ pub fn render_add_logic(
|
||||
f.render_widget(main_block, area);
|
||||
|
||||
// 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 crate::components::common::text_editor::TextEditor::is_vim_insert_mode(&add_logic_state.vim_state) {
|
||||
if add_logic_state.current_focus() == AddLogicFocus::InsideScriptContent {
|
||||
let mut editor_ref = add_logic_state
|
||||
.state
|
||||
.script_content_editor
|
||||
.borrow_mut();
|
||||
|
||||
let border_style_color = if crate::components::common::text_editor::TextEditor::is_vim_insert_mode(add_logic_state.vim_state()) {
|
||||
theme.highlight
|
||||
} else {
|
||||
theme.secondary
|
||||
@@ -44,13 +48,13 @@ pub fn render_add_logic(
|
||||
editor_ref.set_cursor_line_style(Style::default());
|
||||
editor_ref.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
||||
|
||||
let script_title_hint = match add_logic_state.editor_keybinding_mode {
|
||||
let script_title_hint = match add_logic_state.editor_keybinding_mode() {
|
||||
EditorKeybindingMode::Vim => {
|
||||
let vim_mode_status = crate::components::common::text_editor::TextEditor::get_vim_mode_status(&add_logic_state.vim_state);
|
||||
let vim_mode_status = crate::components::common::text_editor::TextEditor::get_vim_mode_status(add_logic_state.vim_state());
|
||||
format!("Script {}", vim_mode_status)
|
||||
}
|
||||
EditorKeybindingMode::Emacs | EditorKeybindingMode::Default => {
|
||||
if crate::components::common::text_editor::TextEditor::is_vim_insert_mode(&add_logic_state.vim_state) {
|
||||
if crate::components::common::text_editor::TextEditor::is_vim_insert_mode(add_logic_state.vim_state()) {
|
||||
"Script (Editing)".to_string()
|
||||
} else {
|
||||
"Script".to_string()
|
||||
@@ -72,10 +76,10 @@ pub fn render_add_logic(
|
||||
drop(editor_ref);
|
||||
|
||||
// === SCRIPT EDITOR AUTOCOMPLETE RENDERING ===
|
||||
if add_logic_state.script_editor_autocomplete_active && !add_logic_state.script_editor_suggestions.is_empty() {
|
||||
if add_logic_state.script_editor_autocomplete_active() && !add_logic_state.script_editor_suggestions().is_empty() {
|
||||
// Get the current cursor position from textarea
|
||||
let current_cursor = {
|
||||
let editor_borrow = add_logic_state.script_content_editor.borrow();
|
||||
let editor_borrow = add_logic_state.script_content_editor().borrow();
|
||||
editor_borrow.cursor() // Returns (row, col) as (usize, usize)
|
||||
};
|
||||
|
||||
@@ -103,8 +107,8 @@ pub fn render_add_logic(
|
||||
input_rect,
|
||||
f.area(), // Full frame area for clamping
|
||||
theme,
|
||||
&add_logic_state.script_editor_suggestions,
|
||||
add_logic_state.script_editor_selected_suggestion_index,
|
||||
add_logic_state.script_editor_suggestions(),
|
||||
add_logic_state.script_editor_selected_suggestion_index(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -128,54 +132,54 @@ pub fn render_add_logic(
|
||||
let buttons_area = main_chunks[3];
|
||||
|
||||
// Top info
|
||||
let table_label = if let Some(name) = add_logic_state.selected_table_name() {
|
||||
name.clone()
|
||||
} else if let Some(id) = add_logic_state.selected_table_id() {
|
||||
format!("ID {}", id)
|
||||
} else {
|
||||
"Global (Not Selected)".to_string()
|
||||
};
|
||||
|
||||
let profile_text = Paragraph::new(vec![
|
||||
Line::from(Span::styled(
|
||||
format!("Profile: {}", add_logic_state.profile_name),
|
||||
Style::default().fg(theme.fg),
|
||||
format!("Profile: {}", add_logic_state.profile_name()),
|
||||
Style::default().fg(theme.fg),
|
||||
)),
|
||||
Line::from(Span::styled(
|
||||
format!(
|
||||
"Table: {}",
|
||||
add_logic_state
|
||||
.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()))
|
||||
),
|
||||
Style::default().fg(theme.fg),
|
||||
format!("Table: {}", table_label),
|
||||
Style::default().fg(theme.fg),
|
||||
)),
|
||||
])
|
||||
.block(
|
||||
Block::default()
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::BOTTOM)
|
||||
.border_style(Style::default().fg(theme.secondary)),
|
||||
);
|
||||
);
|
||||
f.render_widget(profile_text, top_info_area);
|
||||
|
||||
// Canvas - USING CANVAS LIBRARY
|
||||
let focus_on_canvas_inputs = matches!(
|
||||
add_logic_state.current_focus,
|
||||
add_logic_state.current_focus(),
|
||||
AddLogicFocus::InputLogicName
|
||||
| AddLogicFocus::InputTargetColumn
|
||||
| AddLogicFocus::InputDescription
|
||||
);
|
||||
|
||||
let editor = FormEditor::new(add_logic_state.clone());
|
||||
let active_field_rect = render_canvas(f, canvas_area, &editor, theme);
|
||||
let editor = &add_logic_state.editor;
|
||||
let active_field_rect = render_canvas(f, canvas_area, editor, theme);
|
||||
|
||||
// --- Render Autocomplete for Target Column ---
|
||||
if editor.mode() == canvas::AppMode::Edit && editor.current_field() == 1 { // Target Column field
|
||||
if add_logic_state.in_target_column_suggestion_mode && add_logic_state.show_target_column_suggestions {
|
||||
if !add_logic_state.target_column_suggestions.is_empty() {
|
||||
if add_logic_state.in_target_column_suggestion_mode() && add_logic_state.show_target_column_suggestions() {
|
||||
if !add_logic_state.target_column_suggestions().is_empty() {
|
||||
if let Some(input_rect) = active_field_rect {
|
||||
autocomplete::render_autocomplete_dropdown(
|
||||
f,
|
||||
input_rect,
|
||||
f.area(), // Full frame area for clamping
|
||||
theme,
|
||||
&add_logic_state.target_column_suggestions,
|
||||
add_logic_state.selected_target_column_suggestion_index,
|
||||
add_logic_state.target_column_suggestions(),
|
||||
add_logic_state.selected_target_column_suggestion_index(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -184,10 +188,10 @@ pub fn render_add_logic(
|
||||
|
||||
// Script content preview
|
||||
{
|
||||
let mut editor_ref = add_logic_state.script_content_editor.borrow_mut();
|
||||
let mut editor_ref = add_logic_state.script_content_editor().borrow_mut();
|
||||
editor_ref.set_cursor_line_style(Style::default());
|
||||
|
||||
let is_script_preview_focused = add_logic_state.current_focus == AddLogicFocus::ScriptContentPreview;
|
||||
let is_script_preview_focused = add_logic_state.current_focus() == AddLogicFocus::ScriptContentPreview;
|
||||
|
||||
if is_script_preview_focused {
|
||||
editor_ref.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
||||
@@ -256,7 +260,7 @@ pub fn render_add_logic(
|
||||
let save_button = Paragraph::new(" Save Logic ")
|
||||
.style(get_button_style(
|
||||
AddLogicFocus::SaveButton,
|
||||
add_logic_state.current_focus,
|
||||
add_logic_state.current_focus(),
|
||||
))
|
||||
.alignment(Alignment::Center)
|
||||
.block(
|
||||
@@ -264,7 +268,7 @@ pub fn render_add_logic(
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(get_button_border_style(
|
||||
add_logic_state.current_focus == AddLogicFocus::SaveButton,
|
||||
add_logic_state.current_focus() == AddLogicFocus::SaveButton,
|
||||
theme,
|
||||
)),
|
||||
);
|
||||
@@ -273,7 +277,7 @@ pub fn render_add_logic(
|
||||
let cancel_button = Paragraph::new(" Cancel ")
|
||||
.style(get_button_style(
|
||||
AddLogicFocus::CancelButton,
|
||||
add_logic_state.current_focus,
|
||||
add_logic_state.current_focus(),
|
||||
))
|
||||
.alignment(Alignment::Center)
|
||||
.block(
|
||||
@@ -281,7 +285,7 @@ pub fn render_add_logic(
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(get_button_border_style(
|
||||
add_logic_state.current_focus == AddLogicFocus::CancelButton,
|
||||
add_logic_state.current_focus() == AddLogicFocus::CancelButton,
|
||||
theme,
|
||||
)),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/pages/routing/router.rs
|
||||
use crate::state::pages::auth::AuthState;
|
||||
use crate::pages::admin_panel::add_logic::state::AddLogicState;
|
||||
use crate::pages::admin_panel::add_logic::state::AddLogicFormState;
|
||||
use crate::pages::admin_panel::add_table::state::AddTableState;
|
||||
use crate::pages::admin::AdminState;
|
||||
use crate::pages::forms::FormState;
|
||||
@@ -14,7 +14,7 @@ pub enum Page {
|
||||
Login(LoginFormState),
|
||||
Register(RegisterFormState),
|
||||
Admin(AdminState),
|
||||
AddLogic(AddLogicState),
|
||||
AddLogic(AddLogicFormState),
|
||||
AddTable(AddTableState),
|
||||
Form(String),
|
||||
}
|
||||
|
||||
@@ -425,7 +425,18 @@ pub async fn run_ui() -> Result<()> {
|
||||
router.navigate(Page::Admin(admin_state.clone()));
|
||||
}
|
||||
AppView::AddTable => router.navigate(Page::AddTable(admin_state.add_table_state.clone())),
|
||||
AppView::AddLogic => router.navigate(Page::AddLogic(admin_state.add_logic_state.clone())),
|
||||
AppView::AddLogic => {
|
||||
// Create once, like Login/Register
|
||||
if let Page::AddLogic(_) = &router.current {
|
||||
// already on page
|
||||
} else {
|
||||
let mut page = add_logic::state::AddLogicFormState::from_state(
|
||||
admin_state.add_logic_state.clone(),
|
||||
);
|
||||
page.editor.set_keymap(config.build_canvas_keymap());
|
||||
router.navigate(Page::AddLogic(page));
|
||||
}
|
||||
}
|
||||
AppView::Form(path) => {
|
||||
// Keep current_view_* consistent with the active buffer path
|
||||
if let Some((profile, table)) = path.split_once('/') {
|
||||
|
||||
Reference in New Issue
Block a user