feat: add records-mode drill-down with staged edits
Introduce records-mode drill-down functionality that allows users to edit individual records without immediately modifying the underlying model. Key changes: - Added DrillState struct to hold frozen records snapshot and pending edits - New effects: StartDrill, ApplyAndClearDrill, SetDrillPendingEdit - Extended CmdContext with records_col and records_value for records mode - CommitCellEdit now stages edits in pending_edits when in records mode - DrillIntoCell captures a snapshot before switching to drill view - GridLayout supports frozen records for stable view during edits - GridWidget renders with drill_state for pending edit display In records mode, edits are staged and only applied to the model when the user navigates away or commits. This prevents data loss and allows batch editing of records. Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/Qwen3.5-35B-A3B-GGUF:Q5_K_M)
This commit is contained in:
@ -337,6 +337,91 @@ impl Effect for SetTileCatIdx {
|
||||
}
|
||||
}
|
||||
|
||||
/// Populate the drill state with a frozen snapshot of records.
|
||||
/// Clears any previous drill state.
|
||||
#[derive(Debug)]
|
||||
pub struct StartDrill(pub Vec<(CellKey, CellValue)>);
|
||||
impl Effect for StartDrill {
|
||||
fn apply(&self, app: &mut App) {
|
||||
app.drill_state = Some(super::app::DrillState {
|
||||
records: self.0.clone(),
|
||||
pending_edits: std::collections::HashMap::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply any pending edits to the model and clear the drill state.
|
||||
#[derive(Debug)]
|
||||
pub struct ApplyAndClearDrill;
|
||||
impl Effect for ApplyAndClearDrill {
|
||||
fn apply(&self, app: &mut App) {
|
||||
let Some(drill) = app.drill_state.take() else {
|
||||
return;
|
||||
};
|
||||
// For each pending edit, update the cell
|
||||
for ((record_idx, col_name), new_value) in &drill.pending_edits {
|
||||
let Some((orig_key, _)) = drill.records.get(*record_idx) else {
|
||||
continue;
|
||||
};
|
||||
if col_name == "Value" {
|
||||
// Update the cell's value
|
||||
let value = if new_value.is_empty() {
|
||||
app.model.clear_cell(orig_key);
|
||||
continue;
|
||||
} else if let Ok(n) = new_value.parse::<f64>() {
|
||||
CellValue::Number(n)
|
||||
} else {
|
||||
CellValue::Text(new_value.clone())
|
||||
};
|
||||
app.model.set_cell(orig_key.clone(), value);
|
||||
} else {
|
||||
// Rename a coordinate: remove old cell, insert new with updated coord
|
||||
let value = match app.model.get_cell(orig_key) {
|
||||
Some(v) => v.clone(),
|
||||
None => continue,
|
||||
};
|
||||
app.model.clear_cell(orig_key);
|
||||
// Build new key by replacing the coord
|
||||
let new_coords: Vec<(String, String)> = orig_key
|
||||
.0
|
||||
.iter()
|
||||
.map(|(c, i)| {
|
||||
if c == col_name {
|
||||
(c.clone(), new_value.clone())
|
||||
} else {
|
||||
(c.clone(), i.clone())
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let new_key = CellKey::new(new_coords);
|
||||
// Ensure the new item exists in that category
|
||||
if let Some(cat) = app.model.category_mut(col_name) {
|
||||
cat.add_item(new_value.clone());
|
||||
}
|
||||
app.model.set_cell(new_key, value);
|
||||
}
|
||||
}
|
||||
app.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Stage a pending edit in the drill state.
|
||||
#[derive(Debug)]
|
||||
pub struct SetDrillPendingEdit {
|
||||
pub record_idx: usize,
|
||||
pub col_name: String,
|
||||
pub new_value: String,
|
||||
}
|
||||
impl Effect for SetDrillPendingEdit {
|
||||
fn apply(&self, app: &mut App) {
|
||||
if let Some(drill) = &mut app.drill_state {
|
||||
drill
|
||||
.pending_edits
|
||||
.insert((self.record_idx, self.col_name.clone()), self.new_value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Side effects ─────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
Reference in New Issue
Block a user