Point library style variable bindings at imported library variables

Fixes #462

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-15 16:47:58 +02:00
co-authored by Claude Haiku 5.5
parent 102404e8c3
commit 76a67e7ee5
2 changed files with 48 additions and 1 deletions
+18 -1
View File
@@ -582,11 +582,15 @@ fn local_key(lid: &str, key: &str, taken: impl Fn(&str) -> bool) -> String {
}
/// Copy library styles `keys` into `doc` as linked remote styles (with `update`, copies older than
/// the library's are refreshed). Returns library style id → local id.
/// the library's are refreshed). The variables a written copy binds are imported too and its
/// bindings point at their copies (never at this file's variables of the same ids). Returns library
/// style id → local id.
pub fn import_styles(doc: &mut Document, lib: &PublishedLibrary, keys: &BTreeSet<String>, update: bool) -> BTreeMap<String, String> {
let lid = lib.manifest.id.clone();
let mut out = BTreeMap::new();
let mut hashes = BTreeSet::new();
// Local ids of the copies written now, with the library style they copy.
let mut written: Vec<(String, &Style)> = Vec::new();
for key in keys {
let Some(style) = lib.doc.styles.iter().find(|s| s.id == *key) else { continue };
let version = lib.manifest.style(key).map_or(lib.manifest.version, |s| s.version);
@@ -598,6 +602,7 @@ pub fn import_styles(doc: &mut Document, lib: &PublishedLibrary, keys: &BTreeSet
{
*slot = Style { id: r.local.clone(), ..style.clone() };
set_record(&mut doc.libraries_mut().styles, ImportedStyle { version, ..r.clone() });
written.push((r.local.clone(), style));
}
r.local
}
@@ -605,6 +610,7 @@ pub fn import_styles(doc: &mut Document, lib: &PublishedLibrary, keys: &BTreeSet
let local = local_key(&lid, key, |l| doc.styles.iter().any(|s| s.id == l));
doc.styles.push(Style { id: local.clone(), ..style.clone() });
set_record(&mut doc.libraries_mut().styles, ImportedStyle { library: lid.clone(), key: key.clone(), local: local.clone(), version });
written.push((local.clone(), style));
local
}
};
@@ -613,6 +619,17 @@ pub fn import_styles(doc: &mut Document, lib: &PublishedLibrary, keys: &BTreeSet
}
out.insert(key.clone(), local);
}
let bound: BTreeSet<VariableId> = written.iter().flat_map(|(_, s)| s.bound.values().cloned()).collect();
if !bound.is_empty() {
let vars = import_variables(doc, lib, &bound, &BTreeSet::new(), update);
for (local, _) in &written {
if let Some(slot) = doc.styles.iter_mut().find(|s| s.id == *local) {
// Bindings to variables the library lacks are dropped (the published value stays).
slot.bound =
std::mem::take(&mut slot.bound).into_iter().filter_map(|(f, v)| vars.variables.get(&v).map(|l| (f, l.clone()))).collect();
}
}
}
copy_images(doc, &lib.doc, &hashes);
out
}
@@ -314,3 +314,33 @@ fn library_variable_commands_fail_cleanly() {
}
assert!(s.doc().unwrap().libraries.is_empty());
}
#[test]
fn library_style_bindings_ignore_unrelated_local_variables() {
let d = dir("style-bound");
let path = path_str(&d.join("lib.figcraft"));
let mut lib = Session::new();
ex(&mut lib, "variable.createCollection", json!({"name": "Tokens"}));
let var = ex(&mut lib, "variable.create", json!({"collection": "Tokens", "name": "Brand", "type": "color", "value": "#0000ff"}))["id"].clone();
let style = ex(&mut lib, "style.create", json!({"type": "paint", "name": "Brand", "value": "#0000ff"}))["id"].clone();
ex(&mut lib, "style.bind", json!({"style": style, "field": "fill", "variable": var}));
let lid = publish_to(&mut lib, &path);
let mut s = Session::new();
ex(&mut s, "variable.createCollection", json!({"name": "Local"}));
let local = ex(&mut s, "variable.create", json!({"collection": "Local", "name": "Unrelated", "type": "color", "value": "#ff0000"}))["id"].clone();
assert_eq!(local, var, "the ids must collide for this test");
let r = idv(&ex(&mut s, "shape.rectangle", json!({"width": 40, "height": 40})));
ex(&mut s, "library.add", json!({"path": path}));
ex(&mut s, "library.applyStyle", json!({"library": lid, "style": "Brand", "id": r}));
assert_eq!(fill(&s, r), "0000FF");
healthy(&s);
// An updated style follows the library's variable, not the local one.
ex(&mut lib, "variable.set", json!({"variable": "Brand", "value": "#00ffff"}));
republish(&mut lib);
ex(&mut s, "library.acceptUpdates", json!({}));
assert_eq!(fill(&s, r), "00FFFF");
healthy(&s);
let _ = std::fs::remove_dir_all(&d);
}