register has dropdown now

This commit is contained in:
filipriec
2025-08-29 22:07:04 +02:00
parent a09c804595
commit 60eb1c9f51
6 changed files with 149 additions and 60 deletions

View File

@@ -8,6 +8,7 @@ use crate::functions::modes::navigation::add_logic_nav;
use crate::functions::modes::navigation::add_logic_nav::SaveLogicResultSender;
use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender;
use crate::functions::modes::navigation::add_table_nav;
use crate::pages::register::suggestions::RoleSuggestionsProvider;
use crate::pages::admin::main::logic::handle_admin_navigation;
use crate::pages::admin::admin::tui::handle_admin_selection;
use crate::modes::general::command_navigation::{
@@ -44,6 +45,7 @@ use crate::tui::{
};
use crate::ui::handlers::context::UiContext;
use canvas::KeyEventOutcome;
use canvas::SuggestionsProvider;
use anyhow::Result;
use common::proto::komp_ac::search::search_response::Hit;
use crossterm::event::{Event, KeyCode};

View File

@@ -4,6 +4,7 @@
pub mod ui;
pub mod state;
pub mod logic;
pub mod suggestions;
// pub use state::*;
pub use ui::render_register;

View File

@@ -2,16 +2,10 @@
use canvas::{DataProvider, AppMode, FormEditor};
use std::fmt;
use lazy_static::lazy_static;
lazy_static! {
pub static ref AVAILABLE_ROLES: Vec<String> = vec![
"admin".to_string(),
"moderator".to_string(),
"accountant".to_string(),
"viewer".to_string(),
];
}
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use canvas::keymap::KeyEventOutcome;
use crate::pages::register::suggestions::role_suggestions_sync;
/// Represents the state of the Registration form UI
#[derive(Debug, Clone)]
@@ -26,8 +20,6 @@ pub struct RegisterState {
pub current_cursor_pos: usize,
pub has_unsaved_changes: bool,
pub app_mode: canvas::AppMode,
pub role_suggestions: Vec<String>,
pub role_suggestions_active: bool,
}
impl Default for RegisterState {
@@ -43,8 +35,6 @@ impl Default for RegisterState {
current_cursor_pos: 0,
has_unsaved_changes: false,
app_mode: canvas::AppMode::Edit,
role_suggestions: AVAILABLE_ROLES.clone(),
role_suggestions_active: false,
}
}
}
@@ -53,8 +43,6 @@ impl RegisterState {
pub fn new() -> Self {
Self {
app_mode: canvas::AppMode::Edit,
role_suggestions: AVAILABLE_ROLES.clone(),
role_suggestions_active: false,
..Default::default()
}
}
@@ -70,12 +58,6 @@ impl RegisterState {
pub fn set_current_field(&mut self, index: usize) {
if index < 5 {
self.current_field = index;
if index == 4 {
self.activate_role_suggestions();
} else {
self.deactivate_role_suggestions();
}
}
}
@@ -109,28 +91,6 @@ impl RegisterState {
self.app_mode
}
pub fn activate_role_suggestions(&mut self) {
self.role_suggestions_active = true;
let current_input = self.role.to_lowercase();
self.role_suggestions = AVAILABLE_ROLES
.iter()
.filter(|role| role.to_lowercase().contains(&current_input))
.cloned()
.collect();
}
pub fn deactivate_role_suggestions(&mut self) {
self.role_suggestions_active = false;
}
pub fn is_role_suggestions_active(&self) -> bool {
self.role_suggestions_active
}
pub fn get_role_suggestions(&self) -> &[String] {
&self.role_suggestions
}
pub fn has_unsaved_changes(&self) -> bool {
self.has_unsaved_changes
}
@@ -141,9 +101,7 @@ impl RegisterState {
}
impl DataProvider for RegisterState {
fn field_count(&self) -> usize {
5
}
fn field_count(&self) -> usize { 5 }
fn field_name(&self, index: usize) -> &str {
match index {
@@ -308,10 +266,98 @@ impl RegisterFormState {
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 {
// Only customize behavior for the Role field (index 4) in Edit mode
let in_role_field = self.editor.current_field() == 4;
let in_edit_mode = self.editor.mode() == canvas::AppMode::Edit;
if in_role_field && in_edit_mode {
match key_event.code {
// Tab: open suggestions if inactive; otherwise cycle next
KeyCode::Tab => {
if !self.editor.is_suggestions_active() {
if let Some(query) = self.editor.start_suggestions(4) {
let items = role_suggestions_sync(&query);
let applied =
self.editor.apply_suggestions_result(4, &query, items);
if applied {
self.editor.update_inline_completion();
}
}
} else {
// Cycle to next suggestion
self.editor.suggestions_next();
}
return KeyEventOutcome::Consumed(None);
}
// Shift+Tab (BackTab): cycle suggestions too (fallback to next)
KeyCode::BackTab => {
if self.editor.is_suggestions_active() {
// If your canvas exposes suggestions_prev(), use it here.
// Fallback: cycle next.
self.editor.suggestions_next();
return KeyEventOutcome::Consumed(None);
}
}
// Enter: if suggestions active — apply selected suggestion
KeyCode::Enter => {
if self.editor.is_suggestions_active() {
let _ = self.editor.apply_suggestion();
return KeyEventOutcome::Consumed(None);
}
}
// Esc: close suggestions if active
KeyCode::Esc => {
if self.editor.is_suggestions_active() {
self.editor.close_suggestions();
return KeyEventOutcome::Consumed(None);
}
}
// Character input: first let editor mutate text, then refilter if active
KeyCode::Char(_) => {
let outcome = self.editor.handle_key_event(key_event);
if self.editor.is_suggestions_active() {
if let Some(query) = self.editor.start_suggestions(4) {
let items = role_suggestions_sync(&query);
let applied =
self.editor.apply_suggestions_result(4, &query, items);
if applied {
self.editor.update_inline_completion();
}
}
}
return outcome;
}
// Backspace/Delete: mutate then refilter if active
KeyCode::Backspace | KeyCode::Delete => {
let outcome = self.editor.handle_key_event(key_event);
if self.editor.is_suggestions_active() {
if let Some(query) = self.editor.start_suggestions(4) {
let items = role_suggestions_sync(&query);
let applied =
self.editor.apply_suggestions_result(4, &query, items);
if applied {
self.editor.update_inline_completion();
}
}
}
return outcome;
}
_ => { /* fall through to default */ }
}
}
// Default: let canvas handle it
self.editor.handle_key_event(key_event)
}
}

View File

@@ -0,0 +1,36 @@
// src/pages/register/suggestions.rs
use anyhow::Result;
use async_trait::async_trait;
use canvas::{SuggestionItem, SuggestionsProvider};
// Keep the async provider if you want, but add this sync helper and shared data.
const ROLES: &[&str] = &["admin", "moderator", "accountant", "viewer"];
pub fn role_suggestions_sync(query: &str) -> Vec<SuggestionItem> {
let q = query.to_lowercase();
ROLES
.iter()
.filter(|r| q.is_empty() || r.to_lowercase().contains(&q))
.map(|r| SuggestionItem {
display_text: (*r).to_string(),
value_to_store: (*r).to_string(),
})
.collect()
}
pub struct RoleSuggestionsProvider;
#[async_trait]
impl SuggestionsProvider for RoleSuggestionsProvider {
async fn fetch_suggestions(
&mut self,
field_index: usize,
query: &str,
) -> Result<Vec<SuggestionItem>> {
if field_index != 4 {
return Ok(Vec::new());
}
Ok(role_suggestions_sync(query))
}
}

View File

@@ -12,7 +12,10 @@ use ratatui::{
};
use crate::dialog;
use crate::pages::register::RegisterFormState;
use crate::pages::register::suggestions::RoleSuggestionsProvider;
use tokio::runtime::Handle;
use canvas::{render_canvas, render_suggestions_dropdown, DefaultCanvasTheme};
use canvas::SuggestionsProvider;
pub fn render_register(
f: &mut Frame,
@@ -49,9 +52,10 @@ pub fn render_register(
])
.split(inner_area);
// Render the form canvas
// Render the form canvas
let input_rect = render_canvas(f, chunks[0], editor, theme);
// --- HELP TEXT ---
let help_text = Paragraph::new("* are optional fields")
.style(Style::default().fg(theme.fg))
@@ -122,19 +126,6 @@ pub fn render_register(
button_chunks[1],
);
// --- AUTOCOMPLETE DROPDOWN ---
if editor.mode() == canvas::AppMode::Edit {
if let Some(input_rect) = input_rect {
render_suggestions_dropdown(
f,
f.area(),
input_rect,
&DefaultCanvasTheme,
editor,
);
}
}
// --- DIALOG ---
if app_state.ui.dialog.dialog_show {
dialog::render_dialog(
@@ -148,4 +139,17 @@ pub fn render_register(
app_state.ui.dialog.is_loading,
);
}
// Render suggestions dropdown if active (library GUI)
if editor.mode() == canvas::AppMode::Edit {
if let Some(input_rect) = input_rect {
render_suggestions_dropdown(
f,
f.area(),
input_rect,
&DefaultCanvasTheme,
editor,
);
}
}
}