Refactor: split ImportWizard into pure ImportPipeline and UI wrapper
ImportPipeline holds all data and logic (raw JSON, records, proposals, model_name, build_model). ImportWizard wraps it with UI-only state (step, cursor, message). Rename WizardState → WizardStep throughout. Headless import in dispatch.rs now constructs ImportPipeline directly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -5,7 +5,7 @@ use ratatui::{
|
||||
widgets::{Block, Borders, Clear, Widget},
|
||||
};
|
||||
|
||||
use crate::import::wizard::{ImportWizard, WizardState};
|
||||
use crate::import::wizard::{ImportWizard, WizardStep};
|
||||
use crate::import::analyzer::FieldKind;
|
||||
|
||||
pub struct ImportWizardWidget<'a> {
|
||||
@ -28,12 +28,12 @@ impl<'a> Widget for ImportWizardWidget<'a> {
|
||||
|
||||
Clear.render(popup_area, buf);
|
||||
|
||||
let title = match self.wizard.state {
|
||||
WizardState::Preview => " Import Wizard — Step 1: Preview ",
|
||||
WizardState::SelectArrayPath => " Import Wizard — Step 2: Select Array ",
|
||||
WizardState::ReviewProposals => " Import Wizard — Step 3: Review Fields ",
|
||||
WizardState::NameModel => " Import Wizard — Step 4: Name Model ",
|
||||
WizardState::Done => " Import Wizard — Done ",
|
||||
let title = match self.wizard.step {
|
||||
WizardStep::Preview => " Import Wizard — Step 1: Preview ",
|
||||
WizardStep::SelectArrayPath => " Import Wizard — Step 2: Select Array ",
|
||||
WizardStep::ReviewProposals => " Import Wizard — Step 3: Review Fields ",
|
||||
WizardStep::NameModel => " Import Wizard — Step 4: Name Model ",
|
||||
WizardStep::Done => " Import Wizard — Done ",
|
||||
};
|
||||
|
||||
let block = Block::default()
|
||||
@ -47,21 +47,21 @@ impl<'a> Widget for ImportWizardWidget<'a> {
|
||||
let x = inner.x;
|
||||
let w = inner.width as usize;
|
||||
|
||||
match &self.wizard.state {
|
||||
WizardState::Preview => {
|
||||
let summary = self.wizard.preview_summary();
|
||||
match &self.wizard.step {
|
||||
WizardStep::Preview => {
|
||||
let summary = self.wizard.pipeline.preview_summary();
|
||||
buf.set_string(x, y, truncate(&summary, w), Style::default());
|
||||
y += 2;
|
||||
buf.set_string(x, y,
|
||||
"Press Enter to continue…",
|
||||
"Press Enter to continue\u{2026}",
|
||||
Style::default().fg(Color::Yellow));
|
||||
}
|
||||
WizardState::SelectArrayPath => {
|
||||
WizardStep::SelectArrayPath => {
|
||||
buf.set_string(x, y,
|
||||
"Select the path containing records:",
|
||||
Style::default().fg(Color::Yellow));
|
||||
y += 1;
|
||||
for (i, path) in self.wizard.array_paths.iter().enumerate() {
|
||||
for (i, path) in self.wizard.pipeline.array_paths.iter().enumerate() {
|
||||
if y >= inner.y + inner.height { break; }
|
||||
let is_sel = i == self.wizard.cursor;
|
||||
let style = if is_sel {
|
||||
@ -74,9 +74,9 @@ impl<'a> Widget for ImportWizardWidget<'a> {
|
||||
y += 1;
|
||||
}
|
||||
y += 1;
|
||||
buf.set_string(x, y, "↑↓ select Enter confirm", Style::default().fg(Color::DarkGray));
|
||||
buf.set_string(x, y, "\u{2191}\u{2193} select Enter confirm", Style::default().fg(Color::DarkGray));
|
||||
}
|
||||
WizardState::ReviewProposals => {
|
||||
WizardStep::ReviewProposals => {
|
||||
buf.set_string(x, y,
|
||||
"Review field proposals (Space toggle, c cycle kind):",
|
||||
Style::default().fg(Color::Yellow));
|
||||
@ -85,7 +85,7 @@ impl<'a> Widget for ImportWizardWidget<'a> {
|
||||
buf.set_string(x, y, truncate(&header, w), Style::default().fg(Color::Gray).add_modifier(Modifier::UNDERLINED));
|
||||
y += 1;
|
||||
|
||||
for (i, proposal) in self.wizard.proposals.iter().enumerate() {
|
||||
for (i, proposal) in self.wizard.pipeline.proposals.iter().enumerate() {
|
||||
if y >= inner.y + inner.height - 2 { break; }
|
||||
let is_sel = i == self.wizard.cursor;
|
||||
|
||||
@ -96,7 +96,7 @@ impl<'a> Widget for ImportWizardWidget<'a> {
|
||||
FieldKind::Label => Color::DarkGray,
|
||||
};
|
||||
|
||||
let accept_str = if proposal.accepted { "[✓]" } else { "[ ]" };
|
||||
let accept_str = if proposal.accepted { "[\u{2713}]" } else { "[ ]" };
|
||||
let row = format!(" {:<20} {:<22} {}",
|
||||
truncate(&proposal.field, 20),
|
||||
truncate(proposal.kind_label(), 22),
|
||||
@ -117,10 +117,10 @@ impl<'a> Widget for ImportWizardWidget<'a> {
|
||||
buf.set_string(x, hint_y, "Enter: next Space: toggle c: cycle kind Esc: cancel",
|
||||
Style::default().fg(Color::DarkGray));
|
||||
}
|
||||
WizardState::NameModel => {
|
||||
WizardStep::NameModel => {
|
||||
buf.set_string(x, y, "Model name:", Style::default().fg(Color::Yellow));
|
||||
y += 1;
|
||||
let name_str = format!("> {}█", self.wizard.model_name);
|
||||
let name_str = format!("> {}\u{2588}", self.wizard.pipeline.model_name);
|
||||
buf.set_string(x, y, truncate(&name_str, w),
|
||||
Style::default().fg(Color::Green));
|
||||
y += 2;
|
||||
@ -133,7 +133,7 @@ impl<'a> Widget for ImportWizardWidget<'a> {
|
||||
Style::default().fg(Color::Red));
|
||||
}
|
||||
}
|
||||
WizardState::Done => {
|
||||
WizardStep::Done => {
|
||||
buf.set_string(x, y, "Import complete!", Style::default().fg(Color::Green));
|
||||
}
|
||||
}
|
||||
@ -142,6 +142,6 @@ impl<'a> Widget for ImportWizardWidget<'a> {
|
||||
|
||||
fn truncate(s: &str, max: usize) -> String {
|
||||
if s.len() <= max { s.to_string() }
|
||||
else if max > 1 { format!("{}…", &s[..max-1]) }
|
||||
else if max > 1 { format!("{}\u{2026}", &s[..max-1]) }
|
||||
else { s[..max].to_string() }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user