mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-14 19:27:07 -04:00
feat(commands): return IDs from move, duplicate and repeat
Return ordered item IDs from Move, Duplicate and Step and Repeat while preserving the existing moved and created counts, selection and undo behavior. Scripts can use the IDs to operate on copies. Validation: eight independent probes covering both swatch validation and returned IDs, plus all six cargo xtask ci gates on main after #520 (1,188 native tests passed, 3 ignored; all 15 applicable crates passed WASM compilation checks). Both ROADMAP entries and command regression modules are retained. Task: #521
This commit is contained in:
1 parent
5789caaa60
commit
835edae659
2 files changed
+43
-4
No files matched your search
@@ -130,6 +130,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
|
||||
Newest first.
|
||||
|
||||
- **2026-10-12** · Words in capitals hyphenate like capitalized words (FERRUM COM-MODI), as in InDesign; Hyphenate Capitalized Words off keeps them whole. No readiness percentage change.
|
||||
- **2026-10-11** · `transform.move {copy}`, Edit › Duplicate and Step and Repeat return the copies' ids, so a script can go on with them. No readiness percentage change.
|
||||
- **2026-10-11** · `object.fill` and `object.stroke` refuse a swatch name the document doesn't have (it painted nothing) and suggest the name meant (`Paper` → `[Paper]`). No readiness percentage change.
|
||||
- **2026-10-11 (printing):** `file.printers` and `file.print` use craft-libs/craft-print for CUPS and Windows. DesignCraft retains PDF export, page selection, spreads, marks and bleed; dry runs create no temporary PDF or print job. Windows selects matching or default driver paper and shrinks oversized sheets without enlarging smaller ones; queue duplex defaults are preserved. Headless tests exercise exported bytes, job settings, dry runs and refusals; shared native Windows CI passes, including paused-queue submission and cleanup. PostScript, production print controls and physical output validation remain open. No readiness percentage change.
|
||||
|
||||
|
||||
@@ -37,7 +37,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
frame_create
|
||||
),
|
||||
cmd!("line.create", "Create Line", [], None, "{spread?, a: [x,y], b: [x,y]}", has_doc, line_create),
|
||||
cmd!("transform.move", "Move", ["Object", "Transform"], None, "{dx, dy, copy?: bool, ids?, toSpread?}", has_selection, transform_move),
|
||||
cmd!(
|
||||
"transform.move",
|
||||
"Move",
|
||||
["Object", "Transform"],
|
||||
None,
|
||||
"{dx, dy, copy?: bool, ids?, toSpread?} → {moved, ids: the objects moved (with copy, the copies)}",
|
||||
has_selection,
|
||||
transform_move
|
||||
),
|
||||
cmd!(
|
||||
"transform.resize",
|
||||
"Resize",
|
||||
@@ -582,7 +590,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Step and Repeat…",
|
||||
["Edit"],
|
||||
Some("Cmd+Alt+U"),
|
||||
"{count?: 1..1000, dx?, dy?, rows?, columns?, ids?} — copies of the selection (or ids) offset by (dx, dy); with rows/columns, a grid of at most 1000 cells, and at most 1000 item copies including group contents",
|
||||
"{count?: 1..1000, dx?, dy?, rows?, columns?, ids?} — copies of the selection (or ids) offset by (dx, dy); with rows/columns, a grid of at most 1000 cells, and at most 1000 item copies including group contents → {created, ids: the copies}",
|
||||
has_selection,
|
||||
step_and_repeat
|
||||
),
|
||||
@@ -998,7 +1006,7 @@ fn transform_move(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
it.xf = Affine::translate((dx, dy)) * it.xf;
|
||||
}
|
||||
Ok(json!({"moved": ids.len()}))
|
||||
Ok(json!({"moved": ids.len(), "ids": ids.iter().map(|i| i.0).collect::<Vec<_>>()}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2108,7 +2116,8 @@ fn step_and_repeat(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
}
|
||||
*sel = Selection::items(all.clone());
|
||||
Ok(json!({"created": all.len() - ids.len()}))
|
||||
let copies: Vec<u64> = all.iter().skip(ids.len()).map(|i| i.0).collect();
|
||||
Ok(json!({"created": copies.len(), "ids": copies}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2744,6 +2753,35 @@ mod blend_mode_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod copy_ids_tests {
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn copies_report_their_ids() {
|
||||
let mut s = crate::Session::new();
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
let a = s.execute("frame.create", &json!({"rect": [0, 0, 50, 50]})).unwrap()["id"].as_u64().unwrap();
|
||||
let ids = |r: &serde_json::Value| r["ids"].as_array().unwrap().iter().map(|v| v.as_u64().unwrap()).collect::<Vec<_>>();
|
||||
let selected = |s: &crate::Session| s.doc().unwrap().selection.items.iter().map(|i| i.0).collect::<Vec<_>>();
|
||||
// Moving reports what moved; copying reports the copies, which are selected.
|
||||
assert_eq!(ids(&s.execute("transform.move", &json!({"dx": 5, "dy": 0, "ids": [a]})).unwrap()), [a]);
|
||||
let r = s.execute("transform.move", &json!({"dx": 100, "dy": 0, "copy": true, "ids": [a]})).unwrap();
|
||||
let copy = ids(&r);
|
||||
assert!(copy.len() == 1 && copy[0] != a && s.doc().unwrap().doc.item(designcraft_doc::ItemId(copy[0])).is_some(), "{r}");
|
||||
assert_eq!(selected(&s), copy);
|
||||
// Edit › Duplicate.
|
||||
let r = s.execute("edit.duplicate", &json!({})).unwrap();
|
||||
let dup = ids(&r);
|
||||
assert!(dup.len() == 1 && dup[0] != copy[0], "{r}");
|
||||
assert_eq!(selected(&s), dup);
|
||||
// Step and Repeat: every copy.
|
||||
let r = s.execute("edit.stepAndRepeat", &json!({"count": 3, "dx": 0, "dy": 60, "ids": [a]})).unwrap();
|
||||
assert_eq!((r["created"].as_u64(), ids(&r).len()), (Some(3), 3), "{r}");
|
||||
assert!(ids(&r).iter().all(|i| s.doc().unwrap().doc.item(designcraft_doc::ItemId(*i)).is_some()));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod swatch_name_tests {
|
||||
use serde_json::json;
|
||||
|
||||
Reference in new issue
Block a user