mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-28 04:16:46 -06:00
Merge fix/636
This commit is contained in:
@@ -297,6 +297,10 @@ struct Inst<'a> {
|
||||
path: Vec<String>,
|
||||
overrides: Rc<Overrides<'a>>,
|
||||
props: Rc<BTreeMap<String, &'a Kv<'a>>>,
|
||||
/// Slot property GUID → private slot-content node GUID.
|
||||
slot_contents: Rc<BTreeMap<String, String>>,
|
||||
/// Layers imported from a slot's independent content subtree.
|
||||
slot_content: bool,
|
||||
/// Mains being expanded (an instance inside its own main stops there).
|
||||
symbols: Vec<String>,
|
||||
}
|
||||
@@ -770,7 +774,13 @@ impl<'a> Mapper<'a> {
|
||||
// Private provenance for the .fig importer; ordinary REST import ignores it.
|
||||
o.insert(
|
||||
crate::rest::FIG_SOURCE_FIELD.into(),
|
||||
json!({"origin": id, "expanded": inst.is_some(), "wireFrame": own == "FRAME", "wireInstance": own == "INSTANCE"}),
|
||||
json!({
|
||||
"origin": id,
|
||||
"expanded": inst.is_some(),
|
||||
"wireFrame": own == "FRAME",
|
||||
"wireInstance": own == "INSTANCE",
|
||||
"slotContent": inst.is_some_and(|i| i.slot_content),
|
||||
}),
|
||||
);
|
||||
o.insert("type".into(), json!(ty));
|
||||
copy_str(&mut o, &view, "name", "name");
|
||||
@@ -799,6 +809,16 @@ impl<'a> Mapper<'a> {
|
||||
self.component(&mut o, &view, &rest_id);
|
||||
}
|
||||
self.prop_refs(&mut o, &view, inst);
|
||||
let slot_content = if own == "FRAME" {
|
||||
prop_ref(&view, "SLOT_CONTENT_ID").and_then(|def| inst.and_then(|i| i.slot_contents.get(&def).cloned()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if slot_content.is_some()
|
||||
&& let Some(source) = o.get_mut(crate::rest::FIG_SOURCE_FIELD).and_then(Value::as_object_mut)
|
||||
{
|
||||
source.insert("slotOverride".into(), json!(true));
|
||||
}
|
||||
let child_space = if matches!(ty, "GROUP" | "BOOLEAN_OPERATION") { space * view.transform().unwrap_or_default() } else { Affine::IDENTITY };
|
||||
let mut kids = Vec::new();
|
||||
if ty == "INSTANCE" {
|
||||
@@ -810,6 +830,14 @@ impl<'a> Mapper<'a> {
|
||||
kids.extend(self.layer_filtered(&c, Some(&sub), child_space, Some(&view), fit, depth + 1, None));
|
||||
}
|
||||
}
|
||||
} else if let Some(content) = slot_content {
|
||||
let mut content_inst = inst.cloned();
|
||||
if let Some(content_inst) = &mut content_inst {
|
||||
content_inst.slot_content = true;
|
||||
}
|
||||
for c in self.children(&content) {
|
||||
kids.extend(self.layer_filtered(&c, content_inst.as_ref(), child_space, Some(&view), None, depth + 1, included));
|
||||
}
|
||||
} else {
|
||||
// Inside an instance a layer's children are its main's: they follow it from the main's
|
||||
// size to its own.
|
||||
@@ -1338,6 +1366,11 @@ impl<'a> Mapper<'a> {
|
||||
None => continue,
|
||||
},
|
||||
"VARIANT" => json!({"type": "VARIANT"}),
|
||||
"SLOT" => {
|
||||
self.warn_non_default_slot_config(d);
|
||||
self.warn_dropped_slot_preferred_values(d);
|
||||
json!({"type": "SLOT"})
|
||||
}
|
||||
_ => {
|
||||
self.warn(format!("{} component properties are not supported and were skipped", ty.to_ascii_lowercase()));
|
||||
continue;
|
||||
@@ -1354,6 +1387,34 @@ impl<'a> Mapper<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Preferred slot values are source component keys, but this mapper has no proven way to map
|
||||
/// those keys to file GUIDs. They are dropped instead of guessing native NodeIds.
|
||||
fn warn_dropped_slot_preferred_values(&mut self, def: &Kv<'a>) {
|
||||
let entries = def.get("preferredValues").and_then(|v| v.get("instanceSwapValues")).map(Kv::list).unwrap_or_default();
|
||||
if !entries.is_empty() {
|
||||
self.warn("preferred slot component keys cannot be resolved to file nodes and were skipped");
|
||||
}
|
||||
}
|
||||
|
||||
fn warn_non_default_slot_config(&mut self, def: &Kv<'a>) {
|
||||
let Some(config) = def.get("slotPropConfig") else { return };
|
||||
let non_default = [
|
||||
("stretchChildOnInsert", Some(false)),
|
||||
("displayByDefault", Some(false)),
|
||||
("minChildren", None),
|
||||
("maxChildren", None),
|
||||
("allowPreferredValuesOnly", Some(false)),
|
||||
]
|
||||
.into_iter()
|
||||
.any(|(field, bool_default)| {
|
||||
let Some(value) = config.get(field) else { return false };
|
||||
if let Some(default) = bool_default { value.bool() != Some(default) } else { value.f64() != Some(0.0) }
|
||||
});
|
||||
if non_default {
|
||||
self.warn("non-default slot settings are not represented and were skipped");
|
||||
}
|
||||
}
|
||||
|
||||
/// A layer's component property references, and inside an instance the values they take.
|
||||
fn prop_refs(&mut self, o: &mut Obj, v: &View<'a>, inst: Option<&Inst<'a>>) {
|
||||
let mut refs = Obj::new();
|
||||
@@ -1362,6 +1423,7 @@ impl<'a> Mapper<'a> {
|
||||
Some("VISIBLE") => "visible",
|
||||
Some("TEXT_DATA") => "characters",
|
||||
Some("OVERRIDDEN_SYMBOL_ID") => "mainComponent",
|
||||
Some("SLOT_CONTENT_ID") => "slot",
|
||||
_ => continue,
|
||||
};
|
||||
let Some(def) =
|
||||
@@ -1369,7 +1431,11 @@ impl<'a> Mapper<'a> {
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if let Some((name, _)) = self.prop_defs.get(&def) {
|
||||
// Expanded frames must follow their main frame's source link. A direct slot reference
|
||||
// makes the engine resolve ownership against the instance, where the property is not defined.
|
||||
if !(field == "slot" && inst.is_some())
|
||||
&& let Some((name, _)) = self.prop_defs.get(&def)
|
||||
{
|
||||
refs.insert(field.into(), json!(name));
|
||||
}
|
||||
let Some(val) = inst.and_then(|i| i.props.get(&def)) else { continue };
|
||||
@@ -1501,15 +1567,50 @@ impl<'a> Mapper<'a> {
|
||||
rows.sort_by_key(|row| (std::cmp::Reverse(row.source_depth), row.ordinal));
|
||||
}
|
||||
let mut all_props: BTreeMap<String, &'a Kv<'a>> = outer.map(|i| (*i.props).clone()).unwrap_or_default();
|
||||
let mut slot_contents: BTreeMap<String, String> = outer.map(|i| (*i.slot_contents).clone()).unwrap_or_default();
|
||||
for a in assignments {
|
||||
if let (Some(d), Some(val)) = (a.get("defID").and_then(guid), a.get("value")) {
|
||||
all_props.entry(d).or_insert(val);
|
||||
}
|
||||
let Some(def) = a.get("defID").and_then(guid) else { continue };
|
||||
if self.prop_defs.get(&def).is_none_or(|(_, ty)| *ty != "SLOT") {
|
||||
continue;
|
||||
}
|
||||
let var_value = a.get("varValue");
|
||||
if var_value.and_then(|v| v.get("dataType")).and_then(Kv::str) != Some("SLOT_CONTENT_ID") {
|
||||
self.warn("malformed slot content assignment was skipped");
|
||||
continue;
|
||||
}
|
||||
let target = var_value.and_then(|v| v.get("value")).and_then(|v| v.get("slotContentIdValue")).and_then(|v| v.get("guid")).and_then(guid);
|
||||
let Some(target) = target else {
|
||||
self.warn("malformed slot content assignment was skipped");
|
||||
continue;
|
||||
};
|
||||
if target == "4294967295:4294967295" {
|
||||
self.warn("slot content assignment uses the empty GUID sentinel and was skipped");
|
||||
continue;
|
||||
}
|
||||
if !self.nodes.get(&target).is_some_and(|n| n.get("isSlotContent").and_then(Kv::bool) == Some(true)) {
|
||||
self.warn("slot content assignment target is missing or is not slot content");
|
||||
continue;
|
||||
}
|
||||
slot_contents.entry(def).or_insert(target);
|
||||
}
|
||||
let mut symbols = outer.map(|i| i.symbols.clone()).unwrap_or_default();
|
||||
symbols.push(sym.clone());
|
||||
let root = outer.map_or_else(|| rest_id.to_string(), |i| i.root.clone());
|
||||
Some((Inst { root, path: own_path, overrides: Rc::new(overrides), props: Rc::new(all_props), symbols }, sym))
|
||||
Some((
|
||||
Inst {
|
||||
root,
|
||||
path: own_path,
|
||||
overrides: Rc::new(overrides),
|
||||
props: Rc::new(all_props),
|
||||
slot_contents: Rc::new(slot_contents),
|
||||
slot_content: false,
|
||||
symbols,
|
||||
},
|
||||
sym,
|
||||
))
|
||||
}
|
||||
|
||||
fn prototype(&mut self, o: &mut Obj, v: &View<'a>) {
|
||||
|
||||
@@ -21,6 +21,8 @@ pub mod kiwi;
|
||||
mod map;
|
||||
mod network;
|
||||
#[cfg(test)]
|
||||
mod slot_tests;
|
||||
#[cfg(test)]
|
||||
mod source_identity_tests;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
//! Component slot content from `.fig` files: SLOT definitions, the frames that reference them and
|
||||
//! the content subtrees that instances assign, on synthetic messages.
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use super::kiwi::Kv;
|
||||
use super::map::to_rest;
|
||||
|
||||
fn g(local: u32) -> Kv<'static> {
|
||||
Kv::Object(vec![("sessionID", Kv::Uint(9)), ("localID", Kv::Uint(u64::from(local)))])
|
||||
}
|
||||
|
||||
fn o(fields: Vec<(&'static str, Kv<'static>)>) -> Kv<'static> {
|
||||
Kv::Object(fields)
|
||||
}
|
||||
|
||||
fn node(local: u32, kind: &'static str, parent: Option<u32>, mut fields: Vec<(&'static str, Kv<'static>)>) -> Kv<'static> {
|
||||
let mut all = vec![("guid", g(local)), ("type", Kv::Enum(kind)), ("name", Kv::Str("Synthetic".into()))];
|
||||
if let Some(parent) = parent {
|
||||
all.push(("parentIndex", o(vec![("guid", g(parent)), ("position", Kv::Str("!".into()))])));
|
||||
}
|
||||
all.append(&mut fields);
|
||||
o(all)
|
||||
}
|
||||
|
||||
fn mapped(msg: &Kv<'_>) -> Value {
|
||||
to_rest(msg, "Synthetic resources").0
|
||||
}
|
||||
|
||||
fn named_id<'b>(v: &'b Value, id: &str) -> Option<&'b Value> {
|
||||
if v.get("id").and_then(Value::as_str) == Some(id) {
|
||||
return Some(v);
|
||||
}
|
||||
v.get("children").and_then(Value::as_array).into_iter().flatten().find_map(|v| named_id(v, id))
|
||||
}
|
||||
|
||||
fn changes<'a>(msg: &'a mut Kv<'static>) -> &'a mut Vec<Kv<'static>> {
|
||||
let Kv::Object(fields) = msg else { panic!() };
|
||||
let (_, Kv::Array(rows)) = fields.iter_mut().find(|(key, _)| *key == "nodeChanges").unwrap() else { panic!() };
|
||||
rows
|
||||
}
|
||||
|
||||
fn slot_message(content: Option<u32>, preferred: bool) -> Kv<'static> {
|
||||
let slot_ref = o(vec![
|
||||
("variableField", Kv::Enum("SLOT_CONTENT_ID")),
|
||||
("variableData", o(vec![("value", o(vec![("propRefValue", o(vec![("defId", g(70))]))]))])),
|
||||
]);
|
||||
let mut instance_fields = vec![("symbolData", o(vec![("symbolID", g(10))]))];
|
||||
if let Some(content) = content {
|
||||
instance_fields.push((
|
||||
"componentPropAssignments",
|
||||
Kv::Array(vec![o(vec![
|
||||
("defID", g(70)),
|
||||
("value", o(vec![])),
|
||||
(
|
||||
"varValue",
|
||||
o(vec![
|
||||
("value", o(vec![("slotContentIdValue", o(vec![("guid", g(content))]))])),
|
||||
("dataType", Kv::Enum("SLOT_CONTENT_ID")),
|
||||
("resolvedDataType", Kv::Enum("SLOT_CONTENT_ID")),
|
||||
]),
|
||||
),
|
||||
])]),
|
||||
));
|
||||
}
|
||||
let mut definition = vec![("id", g(70)), ("name", Kv::Str("Content".into())), ("type", Kv::Enum("SLOT"))];
|
||||
if preferred {
|
||||
definition.push((
|
||||
"preferredValues",
|
||||
o(vec![("instanceSwapValues", Kv::Array(vec![o(vec![("type", Kv::Enum("COMPONENT")), ("key", Kv::Str("unresolved-key".into()))])]))]),
|
||||
));
|
||||
}
|
||||
o(vec![(
|
||||
"nodeChanges",
|
||||
Kv::Array(vec![
|
||||
node(1, "DOCUMENT", None, vec![]),
|
||||
node(2, "CANVAS", Some(1), vec![]),
|
||||
node(3, "CANVAS", Some(1), vec![("internalOnly", Kv::Bool(true))]),
|
||||
node(10, "SYMBOL", Some(2), vec![("componentPropDefs", Kv::Array(vec![o(definition)]))]),
|
||||
node(12, "FRAME", Some(10), vec![("parameterConsumptionMap", o(vec![("entries", Kv::Array(vec![slot_ref]))]))]),
|
||||
node(13, "RECTANGLE", Some(12), vec![]),
|
||||
node(20, "INSTANCE", Some(2), instance_fields),
|
||||
node(30, "FRAME", Some(3), vec![("isSlotContent", Kv::Bool(true))]),
|
||||
node(31, "RECTANGLE", Some(30), vec![]),
|
||||
node(32, "ELLIPSE", Some(30), vec![]),
|
||||
node(33, "FRAME", Some(3), vec![("isSlotContent", Kv::Bool(true))]),
|
||||
]),
|
||||
)])
|
||||
}
|
||||
#[test]
|
||||
fn slot_definition_reference_and_arbitrary_override_children_are_imported() {
|
||||
let rest = mapped(&slot_message(Some(30), false));
|
||||
let main = named_id(&rest["document"], "9:10").unwrap();
|
||||
assert_eq!(main["componentPropertyDefinitions"]["Content#9:70"]["type"], "SLOT");
|
||||
let main_slot = named_id(&rest["document"], "9:12").unwrap();
|
||||
assert_eq!(main_slot["componentPropertyReferences"]["slot"], "Content#9:70");
|
||||
let slot = named_id(&rest["document"], "I9:20;9:12").unwrap();
|
||||
assert!(slot.get("componentPropertyReferences").is_none());
|
||||
assert_eq!(slot[crate::rest::FIG_SOURCE_FIELD]["slotOverride"], true);
|
||||
let kids = slot["children"].as_array().unwrap();
|
||||
assert_eq!(kids.len(), 2);
|
||||
assert_eq!(kids[0]["id"], "I9:20;9:31");
|
||||
assert_eq!(kids[1]["id"], "I9:20;9:32");
|
||||
|
||||
let (doc, warnings) = crate::rest::import_fig(&rest, &crate::rest::no_images).unwrap();
|
||||
assert!(warnings.is_empty(), "{warnings:?}");
|
||||
let main = doc
|
||||
.node(*doc.pages.first().unwrap())
|
||||
.unwrap()
|
||||
.children
|
||||
.iter()
|
||||
.find_map(|id| doc.node(*id).filter(|n| n.name == "Synthetic" && matches!(n.kind, figcraft_doc::NodeKind::Component { .. })))
|
||||
.unwrap();
|
||||
assert!(
|
||||
matches!(&main.kind, figcraft_doc::NodeKind::Component { props, .. } if matches!(props.get("Content"), Some(figcraft_doc::PropDef::Slot { preferred }) if preferred.is_empty()))
|
||||
);
|
||||
let instance = doc.ids().filter_map(|id| doc.node(id)).find(|n| matches!(n.kind, figcraft_doc::NodeKind::Instance { .. })).unwrap();
|
||||
let slot = doc.node(instance.children[0]).unwrap();
|
||||
assert!(!slot.prop_refs.contains_key("slot"));
|
||||
assert_eq!(slot.source, Some(main.children[0]));
|
||||
assert!(slot.overrides.contains("slot"));
|
||||
assert_eq!(slot.children.len(), 2);
|
||||
assert!(slot.children.iter().all(|id| doc.node(*id).is_some_and(|child| child.source.is_none())));
|
||||
|
||||
let (public, _) = crate::rest::import(&rest, &crate::rest::no_images).unwrap();
|
||||
let public_instance = public.ids().filter_map(|id| public.node(id)).find(|n| matches!(n.kind, figcraft_doc::NodeKind::Instance { .. })).unwrap();
|
||||
let public_slot = public.node(public_instance.children[0]).unwrap();
|
||||
assert!(!public_slot.overrides.contains("slot"));
|
||||
}
|
||||
#[test]
|
||||
fn nested_instance_in_own_slot_content_keeps_its_own_source_tree() {
|
||||
let mut msg = slot_message(Some(30), false);
|
||||
let rows = changes(&mut msg);
|
||||
let slot_frame = rows.iter_mut().find(|row| row.get("guid").and_then(|guid| guid.get("localID")).and_then(Kv::f64) == Some(31.0)).unwrap();
|
||||
let Kv::Object(fields) = slot_frame else { panic!() };
|
||||
fields.iter_mut().find(|(key, _)| *key == "type").unwrap().1 = Kv::Enum("FRAME");
|
||||
rows.extend([
|
||||
node(34, "INSTANCE", Some(31), vec![("symbolData", o(vec![("symbolID", g(40))]))]),
|
||||
node(40, "SYMBOL", Some(2), vec![]),
|
||||
node(41, "FRAME", Some(40), vec![]),
|
||||
node(42, "RECTANGLE", Some(41), vec![]),
|
||||
]);
|
||||
|
||||
let rest = mapped(&msg);
|
||||
let (doc, warnings) = crate::rest::import_fig(&rest, &crate::rest::no_images).unwrap();
|
||||
assert!(warnings.is_empty(), "{warnings:?}");
|
||||
let outer = doc
|
||||
.ids()
|
||||
.filter_map(|id| doc.node(id))
|
||||
.find(|n| {
|
||||
matches!(n.kind, figcraft_doc::NodeKind::Instance { .. })
|
||||
&& !doc.ancestors(n.id).iter().any(|a| doc.node(*a).is_some_and(|p| matches!(p.kind, figcraft_doc::NodeKind::Instance { .. })))
|
||||
})
|
||||
.unwrap();
|
||||
let slot = doc.node(outer.children[0]).unwrap();
|
||||
let own_frame = slot.children.iter().filter_map(|id| doc.node(*id)).find(|n| matches!(n.kind, figcraft_doc::NodeKind::Frame)).unwrap();
|
||||
assert!(own_frame.source.is_none());
|
||||
|
||||
let nested = slot
|
||||
.children
|
||||
.iter()
|
||||
.filter_map(|id| doc.node(*id))
|
||||
.flat_map(|n| n.children.iter())
|
||||
.filter_map(|id| doc.node(*id))
|
||||
.find(|n| matches!(n.kind, figcraft_doc::NodeKind::Instance { .. }))
|
||||
.unwrap();
|
||||
let figcraft_doc::NodeKind::Instance { component, .. } = nested.kind else { panic!("not an instance") };
|
||||
assert_eq!(nested.source, Some(component));
|
||||
let main_frame = doc.node(doc.node(component).unwrap().children[0]).unwrap();
|
||||
let expanded_frame = doc.node(nested.children[0]).unwrap();
|
||||
assert_eq!(expanded_frame.source, Some(main_frame.id));
|
||||
assert_eq!(expanded_frame.children.len(), 1);
|
||||
assert_eq!(doc.node(expanded_frame.children[0]).unwrap().source, Some(main_frame.children[0]));
|
||||
}
|
||||
#[test]
|
||||
fn outer_slot_assignment_wins_over_a_nested_instances_own() {
|
||||
let mut msg = slot_message(Some(30), false);
|
||||
let assignment = |content: u32| {
|
||||
Kv::Array(vec![o(vec![
|
||||
("defID", g(70)),
|
||||
("value", o(vec![])),
|
||||
(
|
||||
"varValue",
|
||||
o(vec![
|
||||
("value", o(vec![("slotContentIdValue", o(vec![("guid", g(content))]))])),
|
||||
("dataType", Kv::Enum("SLOT_CONTENT_ID")),
|
||||
("resolvedDataType", Kv::Enum("SLOT_CONTENT_ID")),
|
||||
]),
|
||||
),
|
||||
])])
|
||||
};
|
||||
// The nested instance inside the main assigns the empty slot content; the outer instance's content wins.
|
||||
let slot_ref = o(vec![
|
||||
("variableField", Kv::Enum("SLOT_CONTENT_ID")),
|
||||
("variableData", o(vec![("value", o(vec![("propRefValue", o(vec![("defId", g(70))]))]))])),
|
||||
]);
|
||||
changes(&mut msg).extend([
|
||||
node(14, "INSTANCE", Some(10), vec![("symbolData", o(vec![("symbolID", g(40))])), ("componentPropAssignments", assignment(33))]),
|
||||
node(40, "SYMBOL", Some(2), vec![]),
|
||||
node(41, "FRAME", Some(40), vec![("parameterConsumptionMap", o(vec![("entries", Kv::Array(vec![slot_ref]))]))]),
|
||||
]);
|
||||
let rest = mapped(&msg);
|
||||
let slot = named_id(&rest["document"], "I9:20;9:14;9:41").unwrap();
|
||||
assert_eq!(slot["children"].as_array().map_or(0, Vec::len), 2, "{slot}");
|
||||
}
|
||||
#[test]
|
||||
fn unassigned_slot_keeps_default_and_bad_content_target_warns() {
|
||||
let rest = mapped(&slot_message(None, false));
|
||||
let slot = named_id(&rest["document"], "I9:20;9:12").unwrap();
|
||||
assert_eq!(slot["children"].as_array().unwrap().len(), 1);
|
||||
assert!(slot.get("overrides").is_none());
|
||||
|
||||
let (rest, warnings) = to_rest(&slot_message(Some(999), false), "Synthetic");
|
||||
assert!(warnings.iter().any(|w| w.contains("slot content assignment target is missing or is not slot content")), "{warnings:?}");
|
||||
let slot = named_id(&rest["document"], "I9:20;9:12").unwrap();
|
||||
assert_eq!(slot["children"].as_array().unwrap().len(), 1);
|
||||
|
||||
let (rest, warnings) = to_rest(&slot_message(Some(33), true), "Synthetic");
|
||||
let main = named_id(&rest["document"], "9:10").unwrap();
|
||||
assert_eq!(main["componentPropertyDefinitions"]["Content#9:70"]["type"], "SLOT");
|
||||
assert!(warnings.iter().any(|w| w.contains("preferred slot component keys cannot be resolved")), "{warnings:?}");
|
||||
let empty = named_id(&rest["document"], "I9:20;9:12").unwrap();
|
||||
assert_eq!(empty["children"].as_array().map_or(0, Vec::len), 0);
|
||||
assert_eq!(empty[crate::rest::FIG_SOURCE_FIELD]["slotOverride"], true);
|
||||
}
|
||||
@@ -17,6 +17,7 @@ struct Origin<'a> {
|
||||
rest: &'a str,
|
||||
guid: &'a str,
|
||||
expanded: bool,
|
||||
slot_content: bool,
|
||||
wire_frame: bool,
|
||||
wire_instance: bool,
|
||||
component: Option<&'a str>,
|
||||
@@ -75,7 +76,8 @@ impl<'a> Sources<'a> {
|
||||
&& meta.get("wireInstance").and_then(Value::as_bool) == Some(true)
|
||||
&& node.get("type").and_then(Value::as_str) == Some("INSTANCE");
|
||||
let component = wire_instance.then(|| node.get("componentId").and_then(Value::as_str).filter(|id| guid(id))).flatten();
|
||||
self.nodes.insert(id, Origin { rest, guid: origin, expanded, wire_frame, wire_instance, component });
|
||||
let slot_content = meta.get("slotContent").and_then(Value::as_bool).unwrap_or(false);
|
||||
self.nodes.insert(id, Origin { rest, guid: origin, expanded, slot_content, wire_frame, wire_instance, component });
|
||||
}
|
||||
|
||||
fn origin(&self, id: NodeId) -> Option<Origin<'a>> {
|
||||
@@ -211,6 +213,9 @@ impl<'a> Sources<'a> {
|
||||
let found = (|| {
|
||||
let meta = self.origin(id)?;
|
||||
let _ = doc.node(id)?;
|
||||
if meta.slot_content {
|
||||
return if matches!(doc.node(id)?.kind, NodeKind::Instance { .. }) { self.root_source(id, doc) } else { None };
|
||||
}
|
||||
if !meta.expanded {
|
||||
return self.root_source(id, doc);
|
||||
}
|
||||
@@ -269,8 +274,8 @@ impl<'a> Sources<'a> {
|
||||
}
|
||||
let mut missing = self.rejected;
|
||||
for (&id, meta) in &self.nodes {
|
||||
let needs_source =
|
||||
(meta.expanded && !self.owned_layer(id, doc, ids)) || doc.node(id).is_some_and(|n| matches!(n.kind, NodeKind::Instance { .. }));
|
||||
let needs_source = (!meta.slot_content && meta.expanded && !self.owned_layer(id, doc, ids))
|
||||
|| doc.node(id).is_some_and(|n| matches!(n.kind, NodeKind::Instance { .. }));
|
||||
if !needs_source {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -451,6 +451,10 @@ impl<'a> Ctx<'a> {
|
||||
let id = self.doc.alloc_id();
|
||||
let label = kind.label();
|
||||
let mut n = Node::new(id, kind, text(j, "name").map_or(label, clip_str), w, h);
|
||||
if self.fig_sources.is_some() && j.get(FIG_SOURCE_FIELD).and_then(|source| source.get("slotOverride")).and_then(Value::as_bool) == Some(true)
|
||||
{
|
||||
n.overrides.insert("slot".into());
|
||||
}
|
||||
n.transform = local(space.world, world);
|
||||
self.common(&mut n, j, parent_mode);
|
||||
let (container, follows, mode) = (n.kind.is_container(), n.kind.bounds_follow_children(), n.layout.as_ref().map(|l| l.mode));
|
||||
@@ -605,7 +609,8 @@ impl<'a> Ctx<'a> {
|
||||
n.child_layout = props::child_layout(j, n.layout.as_ref().map(|l| l.mode), parent_mode);
|
||||
n.exports = props::export_settings(j);
|
||||
for (field, prop) in j.get("componentPropertyReferences").and_then(Value::as_object).into_iter().flatten() {
|
||||
if let (Some(p), true) = (prop.as_str(), matches!(field.as_str(), "visible" | "characters" | "mainComponent")) {
|
||||
let supported = matches!(field.as_str(), "visible" | "characters" | "mainComponent") || (field == "slot" && self.fig_sources.is_some());
|
||||
if let (Some(p), true) = (prop.as_str(), supported) {
|
||||
n.prop_refs.insert(field.clone(), p.to_string());
|
||||
}
|
||||
}
|
||||
@@ -709,6 +714,7 @@ impl<'a> Ctx<'a> {
|
||||
continue;
|
||||
}
|
||||
Some("VARIANT") => continue,
|
||||
Some("SLOT") if self.fig_sources.is_some() => PropDef::Slot { preferred: Vec::new() },
|
||||
_ => {
|
||||
self.warn("unknown component property types were skipped");
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user