M7: imported nested instances link to their main's layers

A nested instance opened from a .fig or file JSON document is first linked to its
own component; matching it to the outer main's layer then went by that component's
name instead of the layer's, so nested instances of variants (or under a name of
their own) and their layers stayed unlinked, and a second export lost what they
changed. They go by their own name until they mirror a layer.
This commit is contained in:
bflatastic
2026-10-13 10:05:39 +07:00
committed by BFlat
parent c910060f36
commit ac6adc2d54
3 changed files with 61 additions and 3 deletions
+5 -2
View File
@@ -420,8 +420,11 @@ fn path_map(doc: &Document, root: NodeId, src_names: bool) -> BTreeMap<NodeId, S
let mut seen: BTreeMap<&str, usize> = BTreeMap::new();
for c in &n.children {
let Some(cn) = doc.node(*c) else { continue };
let name =
if src_names { cn.source.and_then(|s| doc.node(s)).map(|s| s.name.as_str()).unwrap_or(cn.name.as_str()) } else { cn.name.as_str() };
// A nested instance linked only to its own component so far (an imported one) mirrors no
// layer yet: it goes by its own name, like the main's layer it will be matched with.
let unplaced = matches!(&cn.kind, NodeKind::Instance { component, .. } if cn.source == Some(*component));
let mirrored = cn.source.filter(|_| src_names && !unplaced).and_then(|s| doc.node(s));
let name = mirrored.map_or(cn.name.as_str(), |s| s.name.as_str());
let k = seen.entry(name).or_insert(0);
let seg = if *k == 0 { name.to_string() } else { format!("{name}#{k}") };
*k += 1;
@@ -151,3 +151,58 @@ fn every_example_keeps_its_variables_and_styles() {
}
}
}
/// A component holding an instance of a variant, under a name of its own: opened from a `.fig`
/// file, instances of that component link every layer to the main's, the nested instance and its
/// layers included (a nested instance is first linked to its own component, whose name is not the
/// layer's).
#[test]
fn nested_instances_of_variants_link_to_their_mains_layers() {
let mut s = Session::new();
let variant = |s: &mut Session, y: i64, fill: &str| {
let f = id(&run(s, "frame.create", json!({"x": 0, "y": y, "width": 120, "height": 80, "name": "Scene"})));
run(s, "node.set", json!({"ids": [f], "fill": fill}));
run(s, "shape.ellipse", json!({"x": 70, "y": 10, "width": 30, "height": 30, "parent": f, "props": {"name": "Sun"}}));
id(&run(s, "component.create", json!({"ids": [f]})))
};
let (day, dusk) = (variant(&mut s, 0, "#8FCDF2"), variant(&mut s, 120, "#4A3A8E"));
run(&mut s, "node.set", json!({"ids": [day], "name": "Time=Day"}));
run(&mut s, "node.set", json!({"ids": [dusk], "name": "Time=Dusk"}));
run(&mut s, "component.combineVariants", json!({"ids": [day, dusk]}));
let card = id(&run(&mut s, "frame.create", json!({"x": 300, "y": 0, "width": 200, "height": 140, "name": "Card"})));
let photo = id(&run(&mut s, "instance.create", json!({"component": day, "parent": card, "x": 10, "y": 10})));
run(&mut s, "node.set", json!({"ids": [photo], "name": "Photo", "width": 180, "height": 120}));
let card = id(&run(&mut s, "component.create", json!({"ids": [card]})));
let first = id(&run(&mut s, "instance.create", json!({"component": card, "x": 300, "y": 200})));
let second = id(&run(&mut s, "instance.create", json!({"component": card, "x": 600, "y": 200})));
run(&mut s, "node.set", json!({"ids": [first], "name": "First"}));
run(&mut s, "node.set", json!({"ids": [second], "name": "Second"}));
// The second one shows the other variant.
let nested = s.doc().unwrap().node(figcraft_doc::NodeId(second)).unwrap().children[0].0;
run(&mut s, "instance.swap", json!({"ids": [nested], "component": dusk}));
let before = outline(s.doc().unwrap());
let path = std::env::temp_dir().join(format!("figcraft-fig-nested-{}.fig", std::process::id()));
run(&mut s, "file.export", json!({"format": "fig", "path": path.to_string_lossy()}));
let r = run(&mut s, "file.open", json!({"path": path.to_string_lossy()}));
assert_eq!(r["warnings"], json!([]));
let doc = s.doc().unwrap();
assert_eq!(outline(doc), before);
let mut nested = 0;
for inst in doc.nodes().filter(|n| matches!(n.kind, NodeKind::Instance { .. })) {
let inside = doc.ancestors(inst.id).iter().any(|a| doc.node(*a).is_some_and(|x| matches!(x.kind, NodeKind::Instance { .. })));
nested += usize::from(inside);
for layer in doc.subtree(inst.id).into_iter().skip(1).filter_map(|i| doc.node(i)) {
assert!(layer.source.is_some(), "{} in {}", layer.name, inst.name);
}
// Inside another instance, a nested instance mirrors the main's layer, not its component.
if let (true, NodeKind::Instance { component, .. }) = (inside, &inst.kind) {
assert_ne!(inst.source, Some(*component), "{}", inst.name);
}
}
assert_eq!(nested, 2);
// Exported and opened once more, nothing moves: the links were what the export needed.
run(&mut s, "file.export", json!({"format": "fig", "path": path.to_string_lossy()}));
run(&mut s, "file.open", json!({"path": path.to_string_lossy()}));
let _ = std::fs::remove_file(&path);
assert_eq!(outline(s.doc().unwrap()), before);
}