From 5d4a5082ec8188f03ea6107184c771fa0dc7e7c0 Mon Sep 17 00:00:00 2001 From: mortezanaeemi-ops Date: Sun, 1 Nov 2026 14:54:48 +0330 Subject: [PATCH] Add files via upload --- crates/format/src/fig/map.rs | 16 +++++++++---- crates/format/src/fig/tests.rs | 42 +++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/crates/format/src/fig/map.rs b/crates/format/src/fig/map.rs index 98239cd7b..e24a93b2d 100644 --- a/crates/format/src/fig/map.rs +++ b/crates/format/src/fig/map.rs @@ -427,6 +427,8 @@ fn version_rank(v: &str) -> (u64, u64) { pub(super) struct VarCanon { by_key: HashMap, by_id: HashMap, + /// Every live variable node of the file, with a key or not. + local: BTreeSet, } thread_local! { @@ -446,10 +448,12 @@ impl VarCanon { fn install(m: &Mapper) -> VarCanonGuard { let mut newest: HashMap = HashMap::new(); let mut copies: Vec<(String, String)> = Vec::new(); + let mut local = BTreeSet::new(); for (id, n) in &m.nodes { if n.get("type").and_then(Kv::str) != Some("VARIABLE") || n.get("isSoftDeleted").and_then(Kv::bool) == Some(true) { continue; } + local.insert(id.clone()); let Some(key) = n.get("key").and_then(Kv::str).filter(|k| !k.is_empty()) else { continue }; let rank = version_rank(n.get("version").and_then(Kv::str).unwrap_or("")); copies.push((id.clone(), key.to_string())); @@ -465,21 +469,25 @@ impl VarCanon { } } t.by_key = newest.into_iter().map(|(k, (_, id))| (k, id)).collect(); + t.local = local; VAR_CANON.with(|c| *c.borrow_mut() = Some(t)); VarCanonGuard } - /// The variable a reference (`{guid}` or `{assetRef}`) names, as its newest copy. + /// The variable a reference (`{guid}` or `{assetRef}`) names, as its newest copy. A guid + /// counts when this file holds a variable by it; one from another file (a paste carries the + /// library's own guids) names nothing here, so the asset it also names is tried before it is + /// kept as it is. pub(super) fn resolve(r: &Kv) -> Option { let by_guid = r.get("guid").and_then(guid).filter(|g| g != NO_STYLE); VAR_CANON.with(|c| { let c = c.borrow(); let Some(t) = c.as_ref() else { return by_guid.clone() }; - if let Some(g) = &by_guid { + if let Some(g) = by_guid.as_ref().filter(|g| t.local.contains(*g)) { return Some(t.by_id.get(g).cloned().unwrap_or_else(|| g.clone())); } - let key = r.get("assetRef")?.get("key").and_then(Kv::str)?; - t.by_key.get(key).cloned() + let by_asset = r.get("assetRef").and_then(|a| a.get("key")).and_then(Kv::str).and_then(|k| t.by_key.get(k)).cloned(); + by_asset.or(by_guid) }) } diff --git a/crates/format/src/fig/tests.rs b/crates/format/src/fig/tests.rs index 0e2c5016e..d0cea1932 100644 --- a/crates/format/src/fig/tests.rs +++ b/crates/format/src/fig/tests.rs @@ -1522,7 +1522,8 @@ fn library_copies_of_several_versions_become_the_newest() { Some((2, "!")), "VARIABLE_SET", "Products", - vec![("variableSetModes", arr(vec![mode(90, "Blue", "!")])), ("key", s("products")), ("version", s("10:1"))], + // "Legacy" was removed from the library since: only this older copy has it. + vec![("variableSetModes", arr(vec![mode(90, "Blue", "!"), mode(93, "Legacy", "\"")])), ("key", s("products")), ("version", s("10:1"))], ), node( 6, @@ -1557,6 +1558,37 @@ fn library_copies_of_several_versions_become_the_newest() { ("version", s("20:1")), ], ), + // A pasted layer names the variable by the library's own guid (not a node of this file) + // and by its asset: the asset wins. + node( + 11, + Some((1, "\"")), + "FRAME", + "Pasted button", + vec![ + ("size", xy(100.0, 40.0)), + ( + "fillPaints", + arr(vec![o(vec![ + ("type", e("SOLID")), + ("color", rgb(0.0, 0.0, 1.0)), + ( + "colorVar", + o(vec![ + ( + "value", + o(vec![( + "alias", + o(vec![("guid", g(777)), ("assetRef", o(vec![("key", s("button-bg")), ("version", s("20:1"))]))]), + )]), + ), + ("dataType", e("ALIAS")), + ]), + ), + ])]), + ), + ], + ), // The button names the older copy of the variable. node( 10, @@ -1575,14 +1607,18 @@ fn library_copies_of_several_versions_become_the_newest() { let (doc, warnings) = import(&canvas(&schema(), &o(vec![("nodeChanges", arr(nodes))])), "x").unwrap(); assert_eq!(doc.variables.collections.len(), 1, "{:?}", doc.variables.collections); let products = &doc.variables.collections[0]; - assert_eq!(products.modes.iter().map(|m| m.name.as_str()).collect::>(), ["Blue", "Purple"]); + assert_eq!(products.modes.iter().map(|m| m.name.as_str()).collect::>(), ["Blue", "Purple", "Legacy"]); assert_eq!(doc.variables.variables.len(), 1); let var = &doc.variables.variables[0]; assert_eq!(var.collection, products.id); - assert_eq!(var.values.len(), 2, "the newest copy's values: {:?}", var.values); + // The newest copy's values: Purple included (a mode only it has), Legacy filled from the default. + assert_eq!(var.values.len(), 3, "{:?}", var.values); + assert!(matches!(var.values.get("1:92"), Some(figcraft_doc::VariableValue::Color(c)) if (c.r - 0.5).abs() < 1e-6), "{:?}", var.values); let page = doc.nodes().find(|n| n.name == "Page").unwrap(); assert_eq!(page.var_modes.get(&products.id).map(String::as_str), Some("1:92")); let button = named(&doc, "Button"); assert_eq!(button.bound.values().next(), Some(&var.id), "{:?}", button.bound); + let pasted = named(&doc, "Pasted button"); + assert_eq!(pasted.bound.values().next(), Some(&var.id), "{:?}", pasted.bound); assert!(!warnings.iter().any(|w| w.contains("variable modes of collections")), "{warnings:?}"); }