303 lines
11 KiB
Rust
303 lines
11 KiB
Rust
// 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, AddLogicFormState};
|
|
use canvas::{render_canvas, render_suggestions_dropdown, DefaultCanvasTheme, FormEditor};
|
|
use ratatui::{
|
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
|
style::{Modifier, Style},
|
|
text::{Line, Span},
|
|
widgets::{Block, BorderType, Borders, Paragraph},
|
|
Frame,
|
|
};
|
|
use crate::components::common::autocomplete;
|
|
use crate::dialog;
|
|
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 AddLogicFormState,
|
|
) {
|
|
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
|
|
.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
|
|
};
|
|
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 crate::components::common::text_editor::TextEditor::is_vim_insert_mode(add_logic_state.vim_state()) {
|
|
"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 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),
|
|
)),
|
|
Line::from(Span::styled(
|
|
format!("Table: {}", table_label),
|
|
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 - USING CANVAS LIBRARY
|
|
let focus_on_canvas_inputs = matches!(
|
|
add_logic_state.current_focus(),
|
|
AddLogicFocus::InputLogicName
|
|
| AddLogicFocus::InputTargetColumn
|
|
| AddLogicFocus::InputDescription
|
|
);
|
|
|
|
let editor = &add_logic_state.editor;
|
|
let active_field_rect = render_canvas(f, canvas_area, editor, theme);
|
|
|
|
// --- Canvas suggestions dropdown (Target Column, etc.) ---
|
|
if editor.mode() == canvas::AppMode::Edit {
|
|
if let Some(input_rect) = active_field_rect {
|
|
render_suggestions_dropdown(
|
|
f,
|
|
f.area(),
|
|
input_rect,
|
|
&DefaultCanvasTheme,
|
|
editor,
|
|
);
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
);
|
|
}
|
|
}
|