371 lines
12 KiB
Rust
371 lines
12 KiB
Rust
// src/pages/register/state.rs
|
||
|
||
use canvas::{DataProvider, AppMode, FormEditor};
|
||
use std::fmt;
|
||
|
||
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)]
|
||
pub struct RegisterState {
|
||
pub username: String,
|
||
pub email: String,
|
||
pub password: String,
|
||
pub password_confirmation: String,
|
||
pub role: String,
|
||
pub error_message: Option<String>,
|
||
pub current_field: usize,
|
||
pub current_cursor_pos: usize,
|
||
pub has_unsaved_changes: bool,
|
||
pub app_mode: canvas::AppMode,
|
||
}
|
||
|
||
impl Default for RegisterState {
|
||
fn default() -> Self {
|
||
Self {
|
||
username: String::new(),
|
||
email: String::new(),
|
||
password: String::new(),
|
||
password_confirmation: String::new(),
|
||
role: String::new(),
|
||
error_message: None,
|
||
current_field: 0,
|
||
current_cursor_pos: 0,
|
||
has_unsaved_changes: false,
|
||
app_mode: canvas::AppMode::Edit,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl RegisterState {
|
||
pub fn new() -> Self {
|
||
Self {
|
||
app_mode: canvas::AppMode::Edit,
|
||
..Default::default()
|
||
}
|
||
}
|
||
|
||
pub fn current_field(&self) -> usize {
|
||
self.current_field
|
||
}
|
||
|
||
pub fn current_cursor_pos(&self) -> usize {
|
||
self.current_cursor_pos
|
||
}
|
||
|
||
pub fn set_current_field(&mut self, index: usize) {
|
||
if index < 5 {
|
||
self.current_field = index;
|
||
}
|
||
}
|
||
|
||
pub fn set_current_cursor_pos(&mut self, pos: usize) {
|
||
self.current_cursor_pos = pos;
|
||
}
|
||
|
||
pub fn get_current_input(&self) -> &str {
|
||
match self.current_field {
|
||
0 => &self.username,
|
||
1 => &self.email,
|
||
2 => &self.password,
|
||
3 => &self.password_confirmation,
|
||
4 => &self.role,
|
||
_ => "",
|
||
}
|
||
}
|
||
|
||
pub fn get_current_input_mut(&mut self, index: usize) -> &mut String {
|
||
match index {
|
||
0 => &mut self.username,
|
||
1 => &mut self.email,
|
||
2 => &mut self.password,
|
||
3 => &mut self.password_confirmation,
|
||
4 => &mut self.role,
|
||
_ => panic!("Invalid current_field index in RegisterState"),
|
||
}
|
||
}
|
||
|
||
pub fn current_mode(&self) -> AppMode {
|
||
self.app_mode
|
||
}
|
||
|
||
pub fn has_unsaved_changes(&self) -> bool {
|
||
self.has_unsaved_changes
|
||
}
|
||
|
||
pub fn set_has_unsaved_changes(&mut self, changed: bool) {
|
||
self.has_unsaved_changes = changed;
|
||
}
|
||
}
|
||
|
||
impl DataProvider for RegisterState {
|
||
fn field_count(&self) -> usize { 5 }
|
||
|
||
fn field_name(&self, index: usize) -> &str {
|
||
match index {
|
||
0 => "Username",
|
||
1 => "Email (Optional)",
|
||
2 => "Password (Optional)",
|
||
3 => "Confirm Password",
|
||
4 => "Role (Optional)",
|
||
_ => "",
|
||
}
|
||
}
|
||
|
||
fn field_value(&self, index: usize) -> &str {
|
||
match index {
|
||
0 => &self.username,
|
||
1 => &self.email,
|
||
2 => &self.password,
|
||
3 => &self.password_confirmation,
|
||
4 => &self.role,
|
||
_ => "",
|
||
}
|
||
}
|
||
|
||
fn set_field_value(&mut self, index: usize, value: String) {
|
||
match index {
|
||
0 => self.username = value,
|
||
1 => self.email = value,
|
||
2 => self.password = value,
|
||
3 => self.password_confirmation = value,
|
||
4 => self.role = value,
|
||
_ => {}
|
||
}
|
||
self.has_unsaved_changes = true;
|
||
}
|
||
|
||
fn supports_suggestions(&self, field_index: usize) -> bool {
|
||
field_index == 4 // only Role field supports suggestions
|
||
}
|
||
}
|
||
|
||
/// Wrapper that owns both the raw register state and its editor
|
||
pub struct RegisterFormState {
|
||
pub state: RegisterState,
|
||
pub editor: FormEditor<RegisterState>,
|
||
pub focus_outside_canvas: bool,
|
||
pub focused_button_index: usize,
|
||
}
|
||
|
||
impl Default for RegisterFormState {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
// manual Debug because FormEditor doesn’t implement Debug
|
||
impl fmt::Debug for RegisterFormState {
|
||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
f.debug_struct("RegisterFormState")
|
||
.field("state", &self.state)
|
||
.finish()
|
||
}
|
||
}
|
||
|
||
impl RegisterFormState {
|
||
/// Sync the editor's data provider back into our state
|
||
pub fn sync_from_editor(&mut self) {
|
||
// The FormEditor holds the authoritative data
|
||
let dp = self.editor.data_provider();
|
||
self.state = dp.clone(); // because RegisterState: Clone
|
||
}
|
||
|
||
pub fn new() -> Self {
|
||
let state = RegisterState::default();
|
||
let editor = FormEditor::new(state.clone());
|
||
Self {
|
||
state,
|
||
editor,
|
||
focus_outside_canvas: false,
|
||
focused_button_index: 0,
|
||
}
|
||
}
|
||
|
||
// === Delegates to RegisterState ===
|
||
pub fn username(&self) -> &str {
|
||
&self.state.username
|
||
}
|
||
pub fn username_mut(&mut self) -> &mut String {
|
||
&mut self.state.username
|
||
}
|
||
|
||
pub fn email(&self) -> &str {
|
||
&self.state.email
|
||
}
|
||
pub fn email_mut(&mut self) -> &mut String {
|
||
&mut self.state.email
|
||
}
|
||
|
||
pub fn password(&self) -> &str {
|
||
&self.state.password
|
||
}
|
||
pub fn password_mut(&mut self) -> &mut String {
|
||
&mut self.state.password
|
||
}
|
||
|
||
pub fn password_confirmation(&self) -> &str {
|
||
&self.state.password_confirmation
|
||
}
|
||
pub fn password_confirmation_mut(&mut self) -> &mut String {
|
||
&mut self.state.password_confirmation
|
||
}
|
||
|
||
pub fn role(&self) -> &str {
|
||
&self.state.role
|
||
}
|
||
pub fn role_mut(&mut self) -> &mut String {
|
||
&mut self.state.role
|
||
}
|
||
|
||
pub fn error_message(&self) -> Option<&String> {
|
||
self.state.error_message.as_ref()
|
||
}
|
||
pub fn set_error_message(&mut self, msg: Option<String>) {
|
||
self.state.error_message = msg;
|
||
}
|
||
|
||
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 clear(&mut self) {
|
||
self.state.username.clear();
|
||
self.state.email.clear();
|
||
self.state.password.clear();
|
||
self.state.password_confirmation.clear();
|
||
self.state.role.clear();
|
||
self.state.error_message = None;
|
||
self.state.has_unsaved_changes = false;
|
||
self.state.current_field = 0;
|
||
self.state.current_cursor_pos = 0;
|
||
}
|
||
|
||
// === Delegates to cursor/input ===
|
||
pub fn current_field(&self) -> usize {
|
||
self.state.current_field()
|
||
}
|
||
pub fn set_current_field(&mut self, index: usize) {
|
||
self.state.set_current_field(index);
|
||
}
|
||
pub fn current_cursor_pos(&self) -> usize {
|
||
self.state.current_cursor_pos()
|
||
}
|
||
pub fn set_current_cursor_pos(&mut self, pos: usize) {
|
||
self.state.set_current_cursor_pos(pos);
|
||
}
|
||
pub fn get_current_input(&self) -> &str {
|
||
self.state.get_current_input()
|
||
}
|
||
pub fn get_current_input_mut(&mut self) -> &mut String {
|
||
self.state.get_current_input_mut(self.state.current_field)
|
||
}
|
||
|
||
// === Delegates to FormEditor ===
|
||
pub fn mode(&self) -> canvas::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 {
|
||
// 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)
|
||
}
|
||
}
|