diff --git a/src/ui/effect.rs b/src/ui/effect.rs index 5eb1153..de786ff 100644 --- a/src/ui/effect.rs +++ b/src/ui/effect.rs @@ -510,6 +510,137 @@ impl Effect for ExportCsv { } } +/// Load a model from a file, replacing the current one. +#[derive(Debug)] +pub struct LoadModel(pub PathBuf); +impl Effect for LoadModel { + fn apply(&self, app: &mut App) { + match crate::persistence::load(&self.0) { + Ok(mut loaded) => { + loaded.normalize_view_state(); + app.model = loaded; + app.status_msg = format!("Loaded from {}", self.0.display()); + } + Err(e) => { + app.status_msg = format!("Load error: {e}"); + } + } + } +} + +/// Headless JSON/CSV import: read file, analyze, build model, replace current. +#[derive(Debug)] +pub struct ImportJsonHeadless { + pub path: PathBuf, + pub model_name: Option, + pub array_path: Option, +} +impl Effect for ImportJsonHeadless { + fn apply(&self, app: &mut App) { + use crate::import::analyzer::{ + analyze_records, extract_array_at_path, find_array_paths, FieldKind, + }; + use crate::import::wizard::ImportPipeline; + + let is_csv = self + .path + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("csv")); + + let records = if is_csv { + match crate::import::csv_parser::parse_csv(&self.path) { + Ok(recs) => recs, + Err(e) => { + app.status_msg = format!("CSV error: {e}"); + return; + } + } + } else { + let content = match std::fs::read_to_string(&self.path) { + Ok(c) => c, + Err(e) => { + app.status_msg = format!("Cannot read '{}': {e}", self.path.display()); + return; + } + }; + let value: serde_json::Value = match serde_json::from_str(&content) { + Ok(v) => v, + Err(e) => { + app.status_msg = format!("JSON parse error: {e}"); + return; + } + }; + + if let Some(ap) = self.array_path.as_deref().filter(|s| !s.is_empty()) { + match extract_array_at_path(&value, ap) { + Some(arr) => arr.clone(), + None => { + app.status_msg = format!("No array at path '{ap}'"); + return; + } + } + } else if let Some(arr) = value.as_array() { + arr.clone() + } else { + let paths = find_array_paths(&value); + if let Some(first) = paths.first() { + match extract_array_at_path(&value, first) { + Some(arr) => arr.clone(), + None => { + app.status_msg = "Could not extract records array".to_string(); + return; + } + } + } else { + app.status_msg = "No array found in JSON".to_string(); + return; + } + } + }; + + let proposals = analyze_records(&records); + + let raw = if is_csv { + serde_json::Value::Array(records.clone()) + } else { + serde_json::from_str( + &std::fs::read_to_string(&self.path).unwrap_or_default(), + ) + .unwrap_or(serde_json::Value::Array(records.clone())) + }; + + let pipeline = ImportPipeline { + raw, + array_paths: vec![], + selected_path: self.array_path.as_deref().unwrap_or("").to_string(), + records, + proposals: proposals + .into_iter() + .map(|mut p| { + p.accepted = p.kind != FieldKind::Label; + p + }) + .collect(), + model_name: self + .model_name + .as_deref() + .unwrap_or("Imported Model") + .to_string(), + formulas: vec![], + }; + + match pipeline.build_model() { + Ok(new_model) => { + app.model = new_model; + app.status_msg = "Imported successfully".to_string(); + } + Err(e) => { + app.status_msg = format!("Import error: {e}"); + } + } + } +} + #[derive(Debug)] pub struct SetPanelOpen { pub panel: Panel,