migration md
This commit is contained in:
@@ -1,160 +1,334 @@
|
|||||||
# Canvas Crate Migration Documentation
|
# Canvas Library Migration Guide
|
||||||
|
|
||||||
## Files Moved from Client to Canvas
|
## Overview
|
||||||
|
|
||||||
### Core Canvas Files
|
This guide covers the migration from the legacy canvas library structure to the new clean, modular architecture. The new design separates core canvas functionality from autocomplete features, providing better type safety and maintainability.
|
||||||
|
|
||||||
| **Canvas Location** | **Original Client Location** | **Purpose** |
|
## Key Changes
|
||||||
|-------------------|---------------------------|-----------|
|
|
||||||
| `canvas/src/state.rs` | `client/src/state/pages/canvas_state.rs` | Core CanvasState trait |
|
|
||||||
| `canvas/src/actions/edit.rs` | `client/src/functions/modes/edit/form_e.rs` | Generic edit actions |
|
|
||||||
| `canvas/src/renderer.rs` | `client/src/components/handlers/canvas.rs` | Canvas rendering logic |
|
|
||||||
| `canvas/src/modes/highlight.rs` | `client/src/state/app/highlight.rs` | Highlight state types |
|
|
||||||
| `canvas/src/modes/manager.rs` | `client/src/modes/handlers/mode_manager.rs` | Mode management |
|
|
||||||
|
|
||||||
## Import Replacements Needed in Client
|
### 1. **Modular Architecture**
|
||||||
|
```
|
||||||
|
# Old Structure (LEGACY)
|
||||||
|
src/
|
||||||
|
├── state.rs # Mixed canvas + autocomplete
|
||||||
|
├── actions/edit.rs # Mixed concerns
|
||||||
|
├── gui/render.rs # Everything together
|
||||||
|
└── suggestions.rs # Legacy file
|
||||||
|
|
||||||
|
# New Structure (CLEAN)
|
||||||
|
src/
|
||||||
|
├── canvas/ # Core canvas functionality
|
||||||
|
│ ├── state.rs # CanvasState trait only
|
||||||
|
│ ├── actions/edit.rs # Canvas actions only
|
||||||
|
│ └── gui.rs # Canvas rendering
|
||||||
|
├── autocomplete/ # Rich autocomplete features
|
||||||
|
│ ├── state.rs # AutocompleteCanvasState trait
|
||||||
|
│ ├── types.rs # SuggestionItem, AutocompleteState
|
||||||
|
│ ├── actions.rs # Autocomplete actions
|
||||||
|
│ └── gui.rs # Autocomplete dropdown rendering
|
||||||
|
└── dispatcher.rs # Action routing
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Trait Separation**
|
||||||
|
- **CanvasState**: Core form functionality (navigation, input, validation)
|
||||||
|
- **AutocompleteCanvasState**: Optional rich autocomplete features
|
||||||
|
|
||||||
|
### 3. **Rich Suggestions**
|
||||||
|
Replaced simple string suggestions with typed, rich suggestion objects.
|
||||||
|
|
||||||
|
## Migration Steps
|
||||||
|
|
||||||
|
### Step 1: Update Import Paths
|
||||||
|
|
||||||
|
**Find and Replace these imports:**
|
||||||
|
|
||||||
### 1. CanvasState Trait Usage
|
|
||||||
**Replace these imports:**
|
|
||||||
```rust
|
```rust
|
||||||
// OLD
|
# OLD IMPORTS
|
||||||
use crate::state::pages::canvas_state::CanvasState;
|
use canvas::CanvasState;
|
||||||
|
use canvas::CanvasAction;
|
||||||
|
use canvas::ActionContext;
|
||||||
|
use canvas::HighlightState;
|
||||||
|
use canvas::CanvasTheme;
|
||||||
|
use canvas::ActionDispatcher;
|
||||||
|
use canvas::ActionResult;
|
||||||
|
|
||||||
// NEW
|
# NEW IMPORTS
|
||||||
use canvas::canvas::CanvasState;
|
use canvas::canvas::CanvasState;
|
||||||
```
|
use canvas::canvas::CanvasAction;
|
||||||
|
use canvas::canvas::ActionContext;
|
||||||
**Files that need updating:**
|
use canvas::canvas::HighlightState;
|
||||||
- `src/modes/canvas/edit.rs` - Line 9
|
|
||||||
- `src/modes/canvas/read_only.rs` - Line 5
|
|
||||||
- `src/ui/handlers/render.rs` - Line 17
|
|
||||||
- `src/state/pages/auth.rs` - All CanvasState impls
|
|
||||||
- `src/state/pages/form.rs` - CanvasState impl
|
|
||||||
- `src/state/pages/add_table.rs` - CanvasState impl
|
|
||||||
- `src/state/pages/add_logic.rs` - CanvasState impl
|
|
||||||
|
|
||||||
### 2. Edit Actions Usage
|
|
||||||
**Replace these imports:**
|
|
||||||
```rust
|
|
||||||
// OLD
|
|
||||||
use crate::functions::modes::edit::form_e::{execute_edit_action, execute_common_action};
|
|
||||||
|
|
||||||
// NEW
|
|
||||||
use canvas::{execute_edit_action, execute_common_action};
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files that need updating:**
|
|
||||||
- `src/modes/canvas/edit.rs` - Lines 3-5
|
|
||||||
- `src/functions/modes/edit/auth_e.rs`
|
|
||||||
- `src/functions/modes/edit/add_table_e.rs`
|
|
||||||
- `src/functions/modes/edit/add_logic_e.rs`
|
|
||||||
|
|
||||||
### 3. Canvas Rendering Usage
|
|
||||||
**Replace these imports:**
|
|
||||||
```rust
|
|
||||||
// OLD
|
|
||||||
use crate::components::handlers::canvas::render_canvas;
|
|
||||||
|
|
||||||
// NEW
|
|
||||||
use canvas::render_canvas;
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files that need updating:**
|
|
||||||
- Any component that renders forms (login, register, add_table, add_logic, forms)
|
|
||||||
|
|
||||||
### 4. Mode System Usage
|
|
||||||
**Replace these imports:**
|
|
||||||
```rust
|
|
||||||
// OLD
|
|
||||||
use crate::modes::handlers::mode_manager::{AppMode, ModeManager};
|
|
||||||
use crate::state::app::highlight::HighlightState;
|
|
||||||
|
|
||||||
// NEW
|
|
||||||
use canvas::{AppMode, ModeManager, HighlightState};
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files that need updating:**
|
|
||||||
- `src/modes/handlers/event.rs` - Line 14
|
|
||||||
- `src/ui/handlers/ui.rs` - Mode derivation calls
|
|
||||||
- All mode handling files
|
|
||||||
|
|
||||||
## Theme Integration Required
|
|
||||||
|
|
||||||
The canvas crate expects a `CanvasTheme` trait. You need to implement this for your existing theme:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// In client/src/config/colors/themes.rs
|
|
||||||
use canvas::canvas::CanvasTheme;
|
use canvas::canvas::CanvasTheme;
|
||||||
use ratatui::style::Color;
|
use canvas::dispatcher::ActionDispatcher;
|
||||||
|
use canvas::canvas::ActionResult;
|
||||||
|
```
|
||||||
|
|
||||||
impl CanvasTheme for Theme {
|
**Complex imports:**
|
||||||
fn primary_fg(&self) -> Color { self.fg }
|
```rust
|
||||||
fn primary_bg(&self) -> Color { self.bg }
|
# OLD
|
||||||
fn accent(&self) -> Color { self.accent }
|
use canvas::{CanvasAction, ActionDispatcher, ActionResult};
|
||||||
fn warning(&self) -> Color { self.warning }
|
|
||||||
fn secondary(&self) -> Color { self.secondary }
|
# NEW
|
||||||
fn highlight(&self) -> Color { self.highlight }
|
use canvas::{canvas::CanvasAction, dispatcher::ActionDispatcher, canvas::ActionResult};
|
||||||
fn highlight_bg(&self) -> Color { self.highlight_bg }
|
```
|
||||||
|
|
||||||
|
### Step 2: Clean Up State Implementation
|
||||||
|
|
||||||
|
**Remove legacy methods from your CanvasState implementation:**
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl CanvasState for YourFormState {
|
||||||
|
// Keep all the core methods:
|
||||||
|
fn current_field(&self) -> usize { /* ... */ }
|
||||||
|
fn get_current_input(&self) -> &str { /* ... */ }
|
||||||
|
// ... etc
|
||||||
|
|
||||||
|
// ❌ REMOVE these legacy methods:
|
||||||
|
// fn get_suggestions(&self) -> Option<&[String]>
|
||||||
|
// fn get_selected_suggestion_index(&self) -> Option<usize>
|
||||||
|
// fn set_selected_suggestion_index(&mut self, index: Option<usize>)
|
||||||
|
// fn activate_suggestions(&mut self, suggestions: Vec<String>)
|
||||||
|
// fn deactivate_suggestions(&mut self)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Systematic Replacement Strategy
|
### Step 3: Implement Rich Autocomplete (Optional)
|
||||||
|
|
||||||
### Phase 1: Fix Compilation (Do This First)
|
**If you want rich autocomplete features:**
|
||||||
1. Update `client/Cargo.toml` to depend on canvas
|
|
||||||
2. Add theme implementation
|
|
||||||
3. Replace imports in core files
|
|
||||||
|
|
||||||
### Phase 2: Replace Feature-Specific Usage
|
```rust
|
||||||
1. Update auth components
|
use canvas::autocomplete::{AutocompleteCanvasState, SuggestionItem, AutocompleteState};
|
||||||
2. Update form components
|
|
||||||
3. Update admin components
|
|
||||||
4. Update mode handlers
|
|
||||||
|
|
||||||
### Phase 3: Remove Old Files (After Everything Works)
|
impl AutocompleteCanvasState for YourFormState {
|
||||||
1. Delete `src/state/pages/canvas_state.rs`
|
type SuggestionData = YourDataType; // e.g., Hit, CustomRecord, etc.
|
||||||
2. Delete `src/functions/modes/edit/form_e.rs`
|
|
||||||
3. Delete `src/components/handlers/canvas.rs`
|
|
||||||
4. Delete `src/state/app/highlight.rs`
|
|
||||||
5. Delete `src/modes/handlers/mode_manager.rs`
|
|
||||||
|
|
||||||
## Files Safe to Delete After Migration
|
fn supports_autocomplete(&self, field_index: usize) -> bool {
|
||||||
|
// Define which fields support autocomplete
|
||||||
|
matches!(field_index, 2 | 3 | 5) // Example: only certain fields
|
||||||
|
}
|
||||||
|
|
||||||
**These can be removed once imports are updated:**
|
fn autocomplete_state(&self) -> Option<&AutocompleteState<Self::SuggestionData>> {
|
||||||
- `client/src/state/pages/canvas_state.rs`
|
Some(&self.autocomplete)
|
||||||
- `client/src/functions/modes/edit/form_e.rs`
|
}
|
||||||
- `client/src/components/handlers/canvas.rs`
|
|
||||||
- `client/src/state/app/highlight.rs`
|
|
||||||
- `client/src/modes/handlers/mode_manager.rs`
|
|
||||||
|
|
||||||
## Quick Start Commands
|
fn autocomplete_state_mut(&mut self) -> Option<&mut AutocompleteState<Self::SuggestionData>> {
|
||||||
|
Some(&mut self.autocomplete)
|
||||||
```bash
|
}
|
||||||
# 1. Add canvas dependency to client
|
}
|
||||||
cd client
|
|
||||||
echo 'canvas = { path = "../canvas" }' >> Cargo.toml
|
|
||||||
|
|
||||||
# 2. Test compilation
|
|
||||||
cargo check
|
|
||||||
|
|
||||||
# 3. Fix imports one file at a time
|
|
||||||
# Start with: src/config/colors/themes.rs (add CanvasTheme impl)
|
|
||||||
# Then: src/modes/canvas/edit.rs (replace form_e imports)
|
|
||||||
# Then: src/modes/canvas/read_only.rs (replace canvas_state import)
|
|
||||||
|
|
||||||
# 4. After all imports fixed, delete old files
|
|
||||||
rm src/state/pages/canvas_state.rs
|
|
||||||
rm src/functions/modes/edit/form_e.rs
|
|
||||||
rm src/components/handlers/canvas.rs
|
|
||||||
rm src/state/app/highlight.rs
|
|
||||||
rm src/modes/handlers/mode_manager.rs
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Expected Compilation Errors
|
**Add autocomplete field to your state:**
|
||||||
|
```rust
|
||||||
|
pub struct YourFormState {
|
||||||
|
// ... existing fields
|
||||||
|
pub autocomplete: AutocompleteState<YourDataType>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
You'll get errors like:
|
### Step 4: Migrate Suggestions
|
||||||
- `cannot find type 'CanvasState' in this scope`
|
|
||||||
- `cannot find function 'execute_edit_action' in this scope`
|
|
||||||
- `cannot find type 'AppMode' in this scope`
|
|
||||||
|
|
||||||
Fix these by replacing the imports as documented above.
|
**Old way (simple strings):**
|
||||||
|
```rust
|
||||||
|
let suggestions = vec!["John".to_string(), "Jane".to_string()];
|
||||||
|
form_state.activate_suggestions(suggestions);
|
||||||
|
```
|
||||||
|
|
||||||
|
**New way (rich objects):**
|
||||||
|
```rust
|
||||||
|
let suggestions = vec![
|
||||||
|
SuggestionItem::new(
|
||||||
|
hit1, // Your data object
|
||||||
|
"John Doe (Manager) | ID: 123".to_string(), // What user sees
|
||||||
|
"123".to_string(), // What gets stored
|
||||||
|
),
|
||||||
|
SuggestionItem::simple(hit2, "Jane".to_string()), // Simple version
|
||||||
|
];
|
||||||
|
form_state.set_autocomplete_suggestions(suggestions);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Update Rendering
|
||||||
|
|
||||||
|
**Old rendering:**
|
||||||
|
```rust
|
||||||
|
// Manual autocomplete rendering
|
||||||
|
if form_state.autocomplete_active {
|
||||||
|
render_autocomplete_dropdown(/* ... */);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**New rendering:**
|
||||||
|
```rust
|
||||||
|
// Canvas handles everything
|
||||||
|
use canvas::canvas::render_canvas;
|
||||||
|
|
||||||
|
let active_field_rect = render_canvas(f, area, form_state, theme, edit_mode, highlight_state);
|
||||||
|
|
||||||
|
// Optional: Rich autocomplete (if implementing AutocompleteCanvasState)
|
||||||
|
if form_state.is_autocomplete_active() {
|
||||||
|
if let Some(autocomplete_state) = form_state.autocomplete_state() {
|
||||||
|
canvas::autocomplete::render_autocomplete_dropdown(
|
||||||
|
f, f.area(), active_field_rect.unwrap(), theme, autocomplete_state
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Update Method Calls
|
||||||
|
|
||||||
|
**Replace legacy method calls:**
|
||||||
|
```rust
|
||||||
|
# OLD
|
||||||
|
form_state.deactivate_suggestions();
|
||||||
|
|
||||||
|
# NEW - Option A: Add your own method
|
||||||
|
impl YourFormState {
|
||||||
|
pub fn deactivate_autocomplete(&mut self) {
|
||||||
|
self.autocomplete_active = false;
|
||||||
|
self.autocomplete_suggestions.clear();
|
||||||
|
self.selected_suggestion_index = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
form_state.deactivate_autocomplete();
|
||||||
|
|
||||||
|
# NEW - Option B: Use rich autocomplete trait
|
||||||
|
form_state.deactivate_autocomplete(); // If implementing AutocompleteCanvasState
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benefits of New Architecture
|
||||||
|
|
||||||
|
### 1. **Clean Separation of Concerns**
|
||||||
|
- Canvas: Form rendering, navigation, input handling
|
||||||
|
- Autocomplete: Rich suggestions, dropdown management, async loading
|
||||||
|
|
||||||
|
### 2. **Type Safety**
|
||||||
|
```rust
|
||||||
|
// Old: Stringly typed
|
||||||
|
let suggestions: Vec<String> = vec!["user1".to_string()];
|
||||||
|
|
||||||
|
// New: Fully typed with your domain objects
|
||||||
|
let suggestions: Vec<SuggestionItem<UserRecord>> = vec![
|
||||||
|
SuggestionItem::new(user_record, display_text, stored_value)
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. **Rich UX Capabilities**
|
||||||
|
- **Display vs Storage**: Show "John Doe (Manager)" but store user ID
|
||||||
|
- **Loading States**: Built-in spinner/loading indicators
|
||||||
|
- **Async Support**: Designed for async suggestion fetching
|
||||||
|
- **Display Overrides**: Show friendly text while storing normalized data
|
||||||
|
|
||||||
|
### 4. **Future-Proof**
|
||||||
|
- Easy to add new autocomplete features
|
||||||
|
- Canvas features don't interfere with autocomplete
|
||||||
|
- Modular: Use only what you need
|
||||||
|
|
||||||
|
## Advanced Features
|
||||||
|
|
||||||
|
### Display Overrides
|
||||||
|
Perfect for foreign key relationships:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// User selects "John Doe (Manager) | ID: 123"
|
||||||
|
// Field stores: "123" (for database)
|
||||||
|
// User sees: "John Doe" (friendly display)
|
||||||
|
|
||||||
|
impl CanvasState for FormState {
|
||||||
|
fn get_display_value_for_field(&self, index: usize) -> &str {
|
||||||
|
if let Some(display_text) = self.link_display_map.get(&index) {
|
||||||
|
return display_text.as_str(); // Shows "John Doe"
|
||||||
|
}
|
||||||
|
self.inputs().get(index).map(|s| s.as_str()).unwrap_or("") // Shows "123"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_display_override(&self, index: usize) -> bool {
|
||||||
|
self.link_display_map.contains_key(&index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Progressive Enhancement
|
||||||
|
Start simple, add features when needed:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Week 1: Basic usage
|
||||||
|
SuggestionItem::simple(data, "John".to_string());
|
||||||
|
|
||||||
|
// Week 5: Rich display
|
||||||
|
SuggestionItem::new(data, "John Doe (Manager)".to_string(), "John".to_string());
|
||||||
|
|
||||||
|
// Week 10: Store IDs, show names
|
||||||
|
SuggestionItem::new(user, "John Doe (Manager)".to_string(), "123".to_string());
|
||||||
|
```
|
||||||
|
|
||||||
|
## Breaking Changes Summary
|
||||||
|
|
||||||
|
1. **Import paths changed**: Add `canvas::` or `dispatcher::` prefixes
|
||||||
|
2. **Legacy suggestion methods removed**: Replace with rich autocomplete or custom methods
|
||||||
|
3. **No more simple suggestions**: Use `SuggestionItem` for typed suggestions
|
||||||
|
4. **Trait split**: `AutocompleteCanvasState` is now separate and optional
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Compilation Errors
|
||||||
|
|
||||||
|
**Error**: `no method named 'get_suggestions' found`
|
||||||
|
**Fix**: Remove legacy method from `CanvasState` implementation
|
||||||
|
|
||||||
|
**Error**: `no 'CanvasState' in the root`
|
||||||
|
**Fix**: Change `use canvas::CanvasState` to `use canvas::canvas::CanvasState`
|
||||||
|
|
||||||
|
**Error**: `trait bound 'FormState: CanvasState' is not satisfied`
|
||||||
|
**Fix**: Make sure your state properly implements the new `CanvasState` trait
|
||||||
|
|
||||||
|
### Migration Checklist
|
||||||
|
|
||||||
|
- [ ] Updated all import paths
|
||||||
|
- [ ] Removed legacy methods from CanvasState implementation
|
||||||
|
- [ ] Added custom autocomplete methods if needed
|
||||||
|
- [ ] Updated suggestion usage to SuggestionItem
|
||||||
|
- [ ] Updated rendering calls
|
||||||
|
- [ ] Tested form functionality
|
||||||
|
- [ ] Tested autocomplete functionality (if using)
|
||||||
|
|
||||||
|
## Example: Complete Migration
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```rust
|
||||||
|
use canvas::{CanvasState, CanvasAction};
|
||||||
|
|
||||||
|
impl CanvasState for FormState {
|
||||||
|
fn get_suggestions(&self) -> Option<&[String]> { /* ... */ }
|
||||||
|
fn deactivate_suggestions(&mut self) { /* ... */ }
|
||||||
|
// ... other methods
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```rust
|
||||||
|
use canvas::canvas::{CanvasState, CanvasAction};
|
||||||
|
use canvas::autocomplete::{AutocompleteCanvasState, SuggestionItem};
|
||||||
|
|
||||||
|
impl CanvasState for FormState {
|
||||||
|
// Only core canvas methods, no suggestion methods
|
||||||
|
fn current_field(&self) -> usize { /* ... */ }
|
||||||
|
fn get_current_input(&self) -> &str { /* ... */ }
|
||||||
|
// ... other core methods only
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AutocompleteCanvasState for FormState {
|
||||||
|
type SuggestionData = Hit;
|
||||||
|
|
||||||
|
fn supports_autocomplete(&self, field_index: usize) -> bool {
|
||||||
|
self.fields[field_index].is_link
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autocomplete_state(&self) -> Option<&AutocompleteState<Self::SuggestionData>> {
|
||||||
|
Some(&self.autocomplete)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autocomplete_state_mut(&mut self) -> Option<&mut AutocompleteState<Self::SuggestionData>> {
|
||||||
|
Some(&mut self.autocomplete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This migration results in cleaner, more maintainable, and more powerful code!
|
||||||
|
|||||||
Reference in New Issue
Block a user