314 lines
12 KiB
Rust
314 lines
12 KiB
Rust
// src/components/admin/add_logic.rs
|
|
use crate::config::colors::themes::Theme;
|
|
use crate::state::app::highlight::HighlightState;
|
|
use crate::state::app::state::AppState;
|
|
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},
|
|
widgets::{Block, BorderType, Borders, Paragraph},
|
|
Frame,
|
|
};
|
|
use crate::components::handlers::canvas::render_canvas;
|
|
use crate::components::common::{dialog, autocomplete}; // Added autocomplete
|
|
use crate::config::binds::config::EditorKeybindingMode;
|
|
|
|
pub fn render_add_logic(
|
|
f: &mut Frame,
|
|
area: Rect,
|
|
theme: &Theme,
|
|
app_state: &AppState,
|
|
add_logic_state: &mut AddLogicState,
|
|
is_edit_mode: bool,
|
|
highlight_state: &HighlightState,
|
|
) {
|
|
let main_block = Block::default()
|
|
.title(" Add New Logic Script ")
|
|
.title_alignment(Alignment::Center)
|
|
.borders(Borders::ALL)
|
|
.border_type(BorderType::Rounded)
|
|
.border_style(Style::default().fg(theme.border))
|
|
.style(Style::default().bg(theme.bg));
|
|
let inner_area = main_block.inner(area);
|
|
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 is_edit_mode { theme.highlight } else { theme.secondary };
|
|
let border_style = Style::default().fg(border_style_color);
|
|
|
|
editor_ref.set_cursor_line_style(Style::default());
|
|
editor_ref.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
|
|
|
let script_title_hint = match add_logic_state.editor_keybinding_mode {
|
|
EditorKeybindingMode::Vim => {
|
|
let vim_mode_status = 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 is_edit_mode {
|
|
"Script (Editing)".to_string()
|
|
} else {
|
|
"Script".to_string()
|
|
}
|
|
}
|
|
};
|
|
|
|
editor_ref.set_block(
|
|
Block::default()
|
|
.title(Span::styled(script_title_hint, Style::default().fg(theme.fg)))
|
|
.title_alignment(Alignment::Center)
|
|
.borders(Borders::ALL)
|
|
.border_type(BorderType::Rounded)
|
|
.border_style(border_style),
|
|
);
|
|
f.render_widget(&*editor_ref, inner_area);
|
|
|
|
// Drop the editor borrow before accessing autocomplete state
|
|
drop(editor_ref);
|
|
|
|
// === SCRIPT EDITOR AUTOCOMPLETE RENDERING ===
|
|
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();
|
|
editor_borrow.cursor() // Returns (row, col) as (usize, usize)
|
|
};
|
|
|
|
let (cursor_line, cursor_col) = current_cursor;
|
|
|
|
// Account for TextArea's block borders (1 for each side)
|
|
let block_offset_x = 1;
|
|
let block_offset_y = 1;
|
|
|
|
// Position autocomplete at current cursor position
|
|
// Add 1 to column to position dropdown right after the cursor
|
|
let autocomplete_x = cursor_col + 1;
|
|
let autocomplete_y = cursor_line;
|
|
|
|
let input_rect = Rect {
|
|
x: (inner_area.x + block_offset_x + autocomplete_x as u16).min(inner_area.right().saturating_sub(20)),
|
|
y: (inner_area.y + block_offset_y + autocomplete_y as u16).min(inner_area.bottom().saturating_sub(5)),
|
|
width: 1, // Minimum width for positioning
|
|
height: 1,
|
|
};
|
|
|
|
// Render autocomplete dropdown
|
|
autocomplete::render_autocomplete_dropdown(
|
|
f,
|
|
input_rect,
|
|
f.area(), // Full frame area for clamping
|
|
theme,
|
|
&add_logic_state.script_editor_suggestions,
|
|
add_logic_state.script_editor_selected_suggestion_index,
|
|
);
|
|
}
|
|
|
|
return; // Exit early for fullscreen mode
|
|
}
|
|
|
|
// Regular layout with preview
|
|
let main_chunks = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints([
|
|
Constraint::Length(3), // Top info
|
|
Constraint::Length(9), // Canvas for 3 inputs (each 1 line + 1 padding = 2 lines * 3 + 2 border = 8, +1 for good measure)
|
|
Constraint::Min(5), // Script preview
|
|
Constraint::Length(3), // Buttons
|
|
])
|
|
.split(inner_area);
|
|
|
|
let top_info_area = main_chunks[0];
|
|
let canvas_area = main_chunks[1];
|
|
let script_content_area = main_chunks[2];
|
|
let buttons_area = main_chunks[3];
|
|
|
|
// Top info
|
|
let profile_text = Paragraph::new(vec![
|
|
Line::from(Span::styled(
|
|
format!("Profile: {}", add_logic_state.profile_name),
|
|
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),
|
|
)),
|
|
])
|
|
.block(
|
|
Block::default()
|
|
.borders(Borders::BOTTOM)
|
|
.border_style(Style::default().fg(theme.secondary)),
|
|
);
|
|
f.render_widget(profile_text, top_info_area);
|
|
|
|
// Canvas
|
|
let focus_on_canvas_inputs = matches!(
|
|
add_logic_state.current_focus,
|
|
AddLogicFocus::InputLogicName
|
|
| AddLogicFocus::InputTargetColumn
|
|
| AddLogicFocus::InputDescription
|
|
);
|
|
// Call render_canvas and get the active_field_rect
|
|
let active_field_rect = render_canvas(
|
|
f,
|
|
canvas_area,
|
|
add_logic_state, // Pass the whole state as it impl CanvasState
|
|
&add_logic_state.fields(),
|
|
&add_logic_state.current_field(),
|
|
&add_logic_state.inputs(),
|
|
theme,
|
|
is_edit_mode && focus_on_canvas_inputs, // is_edit_mode for canvas fields
|
|
highlight_state,
|
|
);
|
|
|
|
// --- Render Autocomplete for Target Column ---
|
|
// `is_edit_mode` here refers to the general edit mode of the EventHandler
|
|
if is_edit_mode && add_logic_state.current_field() == 1 { // Target Column field
|
|
if let Some(suggestions) = add_logic_state.get_suggestions() { // Uses CanvasState impl
|
|
let selected = add_logic_state.get_selected_suggestion_index();
|
|
if !suggestions.is_empty() { // Only render if there are suggestions to show
|
|
if let Some(input_rect) = active_field_rect {
|
|
autocomplete::render_autocomplete_dropdown(
|
|
f,
|
|
input_rect,
|
|
f.area(), // Full frame area for clamping
|
|
theme,
|
|
suggestions,
|
|
selected,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Script content preview
|
|
{
|
|
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;
|
|
|
|
if is_script_preview_focused {
|
|
editor_ref.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
|
} else {
|
|
let underscore_cursor_style = Style::default()
|
|
.add_modifier(Modifier::UNDERLINED)
|
|
.fg(theme.secondary);
|
|
editor_ref.set_cursor_style(underscore_cursor_style);
|
|
}
|
|
|
|
let border_style_color = if is_script_preview_focused {
|
|
theme.highlight
|
|
} else {
|
|
theme.secondary
|
|
};
|
|
|
|
let title_text = "Script Preview"; // Title doesn't need to change based on focus here
|
|
|
|
let title_style = if is_script_preview_focused {
|
|
Style::default().fg(theme.highlight).add_modifier(Modifier::BOLD)
|
|
} else {
|
|
Style::default().fg(theme.fg)
|
|
};
|
|
|
|
editor_ref.set_block(
|
|
Block::default()
|
|
.title(Span::styled(title_text, title_style))
|
|
.borders(Borders::ALL)
|
|
.border_type(BorderType::Rounded)
|
|
.border_style(Style::default().fg(border_style_color)),
|
|
);
|
|
f.render_widget(&*editor_ref, script_content_area);
|
|
}
|
|
|
|
// Buttons
|
|
let get_button_style = |button_focus: AddLogicFocus, current_focus_state: AddLogicFocus| {
|
|
let is_focused = current_focus_state == button_focus;
|
|
let base_style = Style::default().fg(if is_focused {
|
|
theme.highlight
|
|
} else {
|
|
theme.secondary
|
|
});
|
|
if is_focused {
|
|
base_style.add_modifier(Modifier::BOLD)
|
|
} else {
|
|
base_style
|
|
}
|
|
};
|
|
|
|
let get_button_border_style = |is_focused: bool, current_theme: &Theme| {
|
|
if is_focused {
|
|
Style::default().fg(current_theme.highlight)
|
|
} else {
|
|
Style::default().fg(current_theme.secondary)
|
|
}
|
|
};
|
|
|
|
let button_chunks = Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints([
|
|
Constraint::Percentage(50),
|
|
Constraint::Percentage(50),
|
|
])
|
|
.split(buttons_area);
|
|
|
|
let save_button = Paragraph::new(" Save Logic ")
|
|
.style(get_button_style(
|
|
AddLogicFocus::SaveButton,
|
|
add_logic_state.current_focus,
|
|
))
|
|
.alignment(Alignment::Center)
|
|
.block(
|
|
Block::default()
|
|
.borders(Borders::ALL)
|
|
.border_type(BorderType::Rounded)
|
|
.border_style(get_button_border_style(
|
|
add_logic_state.current_focus == AddLogicFocus::SaveButton,
|
|
theme,
|
|
)),
|
|
);
|
|
f.render_widget(save_button, button_chunks[0]);
|
|
|
|
let cancel_button = Paragraph::new(" Cancel ")
|
|
.style(get_button_style(
|
|
AddLogicFocus::CancelButton,
|
|
add_logic_state.current_focus,
|
|
))
|
|
.alignment(Alignment::Center)
|
|
.block(
|
|
Block::default()
|
|
.borders(Borders::ALL)
|
|
.border_type(BorderType::Rounded)
|
|
.border_style(get_button_border_style(
|
|
add_logic_state.current_focus == AddLogicFocus::CancelButton,
|
|
theme,
|
|
)),
|
|
);
|
|
f.render_widget(cancel_button, button_chunks[1]);
|
|
|
|
// Dialog
|
|
if app_state.ui.dialog.dialog_show {
|
|
dialog::render_dialog(
|
|
f,
|
|
f.area(),
|
|
theme,
|
|
&app_state.ui.dialog.dialog_title,
|
|
&app_state.ui.dialog.dialog_message,
|
|
&app_state.ui.dialog.dialog_buttons,
|
|
app_state.ui.dialog.dialog_active_button_index,
|
|
app_state.ui.dialog.is_loading,
|
|
);
|
|
}
|
|
}
|