Builds out two half-finished view features: Group collapse: - AxisEntry enum distinguishes GroupHeader from DataItem on grid axes - expand_category() emits group headers and filters collapsed items - Grid renders inline group header rows with ▼/▶ indicator - `z` keybinding toggles collapse of nearest group above cursor Hide/show item: - Restore show_item() (was commented out alongside hide_item) - Add HideItem / ShowItem commands and dispatch - `H` keybinding hides the current row item - `:show-item <cat> <item>` command to restore hidden items - Restore silenced test assertions for hide/show round-trip Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
125 lines
3.2 KiB
Rust
125 lines
3.2 KiB
Rust
use crate::view::Axis;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// All commands that can mutate a Model.
|
|
///
|
|
/// Serialized as `{"op": "<variant>", ...rest}` where `rest` contains
|
|
/// the variant's fields flattened into the same JSON object.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(tag = "op")]
|
|
pub enum Command {
|
|
/// Add a category (dimension).
|
|
AddCategory { name: String },
|
|
|
|
/// Add an item to a category.
|
|
AddItem { category: String, item: String },
|
|
|
|
/// Add an item inside a named group.
|
|
AddItemInGroup {
|
|
category: String,
|
|
item: String,
|
|
group: String,
|
|
},
|
|
|
|
/// Set a cell value. `coords` is a list of `[category, item]` pairs.
|
|
SetCell {
|
|
coords: Vec<[String; 2]>,
|
|
#[serde(flatten)]
|
|
value: CellValueArg,
|
|
},
|
|
|
|
/// Clear a cell.
|
|
ClearCell { coords: Vec<[String; 2]> },
|
|
|
|
/// Add or replace a formula.
|
|
/// `raw` is the full formula string, e.g. "Profit = Revenue - Cost".
|
|
/// `target_category` names the category that owns the formula target.
|
|
AddFormula {
|
|
raw: String,
|
|
target_category: String,
|
|
},
|
|
|
|
/// Remove a formula by its target name and category.
|
|
RemoveFormula {
|
|
target: String,
|
|
target_category: String,
|
|
},
|
|
|
|
/// Create a new view.
|
|
CreateView { name: String },
|
|
|
|
/// Delete a view.
|
|
DeleteView { name: String },
|
|
|
|
/// Switch the active view.
|
|
SwitchView { name: String },
|
|
|
|
/// Set the axis of a category in the active view.
|
|
SetAxis { category: String, axis: Axis },
|
|
|
|
/// Set the page-axis selection for a category.
|
|
SetPageSelection { category: String, item: String },
|
|
|
|
/// Toggle collapse of a group in the active view.
|
|
ToggleGroup { category: String, group: String },
|
|
|
|
/// Hide an item in the active view.
|
|
HideItem { category: String, item: String },
|
|
|
|
/// Show (un-hide) an item in the active view.
|
|
ShowItem { category: String, item: String },
|
|
|
|
/// Save the model to a file path.
|
|
Save { path: String },
|
|
|
|
/// Load a model from a file path (replaces current model).
|
|
Load { path: String },
|
|
|
|
/// Export the active view to CSV.
|
|
ExportCsv { path: String },
|
|
|
|
/// Import a JSON file via the analyzer (non-interactive, uses auto-detected proposals).
|
|
ImportJson {
|
|
path: String,
|
|
model_name: Option<String>,
|
|
/// Dot-path to the records array (empty = root)
|
|
array_path: Option<String>,
|
|
},
|
|
}
|
|
|
|
/// Inline value for SetCell
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(untagged)]
|
|
pub enum CellValueArg {
|
|
Number { number: f64 },
|
|
Text { text: String },
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CommandResult {
|
|
pub ok: bool,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub message: Option<String>,
|
|
}
|
|
|
|
impl CommandResult {
|
|
pub fn ok() -> Self {
|
|
Self {
|
|
ok: true,
|
|
message: None,
|
|
}
|
|
}
|
|
pub fn ok_msg(msg: impl Into<String>) -> Self {
|
|
Self {
|
|
ok: true,
|
|
message: Some(msg.into()),
|
|
}
|
|
}
|
|
pub fn err(msg: impl Into<String>) -> Self {
|
|
Self {
|
|
ok: false,
|
|
message: Some(msg.into()),
|
|
}
|
|
}
|
|
}
|