Add files via upload

This commit is contained in:
mortezanaeemi-ops authored and GitHub committed 2026-11-01 15:34:41 +03:30
1 parent 5d4a5082ec
commit c411152b87
2 files changed
+88 -12

No files matched your search

+34 -11
View File
@@ -372,22 +372,23 @@ impl<'a> Mapper<'a> {
/// while the mode ids stay the same across them, so every copy maps to the newest one (the
/// one [`Self::variables`] keeps, with the modes of the others added). Without this, a page set
/// to a mode an older copy didn't have yet lost its mode, and the layers fell back to the
/// default mode of the newest copy.
/// default mode of the newest copy. A reference by asset that names no copy by itself (no
/// version, or a version the file doesn't hold, while it holds several) still names the asset:
/// its newest live copy.
pub(super) fn collection_id(&self, v: &Kv) -> Option<String> {
let id = self.resource_id(v)?;
Some(self.canonical_collection(&id).unwrap_or(id))
let by_asset = || v.get("assetRef")?.get("key").and_then(Kv::str).and_then(VarCanon::newest_set);
match self.resource_id(v) {
Some(id) if self.nodes.contains_key(&id) => Some(self.canonical_collection(&id).unwrap_or(id)),
// The asset index can name a node a later change removed: the asset's live copy instead.
Some(id) => by_asset().or(Some(id)),
None => by_asset(),
}
}
/// The newest live copy of collection node `id`'s library asset, when it has a key.
pub(super) fn canonical_collection(&self, id: &str) -> Option<String> {
let key = self.nodes.get(id)?.get("key").and_then(Kv::str).filter(|k| !k.is_empty())?;
let live_set = |n: &Kv| n.get("type").and_then(Kv::str) == Some("VARIABLE_SET") && n.get("isSoftDeleted").and_then(Kv::bool) != Some(true);
self.assets
.get(key)?
.iter()
.filter(|(_, nid)| self.nodes.get(nid.as_str()).is_some_and(|n| live_set(n)))
.max_by_key(|(ver, _)| version_rank(ver))
.map(|(_, nid)| nid.clone())
VarCanon::newest_set(key)
}
/// A note about what this file's mapping left out or changed. Counted, as the REST importer
@@ -429,6 +430,10 @@ pub(super) struct VarCanon {
by_id: HashMap<String, String>,
/// Every live variable node of the file, with a key or not.
local: BTreeSet<String>,
/// The newest live copy of each library collection, by asset key. Read from the file's live
/// nodes (the last change of each guid, REMOVED ones gone), not from the asset index, which
/// keeps the first node it met for a key and version even when a later change removed it.
sets_by_key: HashMap<String, String>,
}
thread_local! {
@@ -449,8 +454,20 @@ impl VarCanon {
let mut newest: HashMap<String, ((u64, u64), String)> = HashMap::new();
let mut copies: Vec<(String, String)> = Vec::new();
let mut local = BTreeSet::new();
let mut sets: HashMap<String, ((u64, u64), String)> = HashMap::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) {
let ty = n.get("type").and_then(Kv::str);
if !matches!(ty, Some("VARIABLE" | "VARIABLE_SET")) || n.get("isSoftDeleted").and_then(Kv::bool) == Some(true) {
continue;
}
if ty == Some("VARIABLE_SET") {
if let Some(key) = n.get("key").and_then(Kv::str).filter(|k| !k.is_empty()) {
let rank = version_rank(n.get("version").and_then(Kv::str).unwrap_or(""));
let e = sets.entry(key.to_string()).or_insert_with(|| (rank, id.clone()));
if rank > e.0 || (rank == e.0 && *id > e.1) {
*e = (rank, id.clone());
}
}
continue;
}
local.insert(id.clone());
@@ -470,6 +487,7 @@ impl VarCanon {
}
t.by_key = newest.into_iter().map(|(k, (_, id))| (k, id)).collect();
t.local = local;
t.sets_by_key = sets.into_iter().map(|(k, (_, id))| (k, id)).collect();
VAR_CANON.with(|c| *c.borrow_mut() = Some(t));
VarCanonGuard
}
@@ -491,6 +509,11 @@ impl VarCanon {
})
}
/// The newest live copy of the library collection with asset key `key`.
pub(super) fn newest_set(key: &str) -> Option<String> {
VAR_CANON.with(|c| c.borrow().as_ref().and_then(|t| t.sets_by_key.get(key).cloned()))
}
/// Whether variable node `id` is an older copy another one replaces.
pub(super) fn superseded(id: &str) -> bool {
VAR_CANON.with(|c| c.borrow().as_ref().and_then(|t| t.by_id.get(id)).is_some_and(|canon| canon != id))
+54 -1
View File
@@ -28,6 +28,7 @@ enum StrokeCap NONE ROUND SQUARE ARROW_LINES ARROW_EQUILATERAL
enum StrokeJoin MITER BEVEL ROUND
enum VectorMirror NONE ANGLE ANGLE_AND_LENGTH
enum ShapeWithTextType SQUARE ELLIPSE
enum NodePhase CREATED REMOVED
enum ConnectorLineStyle ELBOWED STRAIGHT CURVED
enum ExpressionFunction ADDITION SUBTRACTION RESOLVE_VARIANT MULTIPLY DIVIDE EQUALS
enum NavigationType NAVIGATE OVERLAY
@@ -74,7 +75,7 @@ message Event InteractionType interactionType
message Action GUID transitionNodeID, TransitionType transitionType, float transitionDuration, EasingType easingType, ConnectionType connectionType, NavigationType navigationType, VariableTarget targetVariable, VariableData targetVariableData, VariableSetID targetVariableSetID, GUID targetVariableModeID, ConditionalActions[] conditionalActions
message Interaction Event event, Action[] actions
message StartingPoint string name, string position
message NodeChange GUID guid, ParentIndex parentIndex, NodeType type, string name, bool visible, float opacity, Vector size, Matrix transform, Paint[] fillPaints, Paint[] strokePaints, float strokeWeight, float cornerRadius, bool resizeToFit, bool internalOnly, float fontSize, FontName fontName, TextData textData, uint styleID, Path[] fillGeometry, Path[] strokeGeometry, StyleType styleType, StyleId styleIdForFill, SymbolData symbolData, PropDef[] componentPropDefs, PropAssignment[] componentPropAssignments, VariableDataMap parameterConsumptionMap, SetMode[] variableSetModes, VariableID variableSetID, ResolvedType variableResolvedType, Values variableDataValues, ModeMap variableModeBySetMap, Interaction[] prototypeInteractions, StartingPoint prototypeStartingPoint, GUIDPath guidPath, StackMode stackMode, float stackSpacing, float stackHorizontalPadding, float stackVerticalPadding, uint count, float starInnerScale, VectorData vectorData, StrokeCap strokeCap, StrokeJoin strokeJoin, VectorMirror handleMirroring, ShapeWithTextType shapeWithTextType, ConnectorEndpoint connectorStart, ConnectorEndpoint connectorEnd, StrokeCap connectorStartCap, StrokeCap connectorEndCap, ConnectorLineStyle connectorLineStyle, ConstraintType horizontalConstraint, ConstraintType verticalConstraint, NodeChange[] derivedSymbolData, string key, string version, LeadingTrim leadingTrim, float[] dashPattern
message NodeChange GUID guid, ParentIndex parentIndex, NodeType type, string name, bool visible, float opacity, Vector size, Matrix transform, Paint[] fillPaints, Paint[] strokePaints, float strokeWeight, float cornerRadius, bool resizeToFit, bool internalOnly, float fontSize, FontName fontName, TextData textData, uint styleID, Path[] fillGeometry, Path[] strokeGeometry, StyleType styleType, StyleId styleIdForFill, SymbolData symbolData, PropDef[] componentPropDefs, PropAssignment[] componentPropAssignments, VariableDataMap parameterConsumptionMap, SetMode[] variableSetModes, VariableID variableSetID, ResolvedType variableResolvedType, Values variableDataValues, ModeMap variableModeBySetMap, Interaction[] prototypeInteractions, StartingPoint prototypeStartingPoint, GUIDPath guidPath, StackMode stackMode, float stackSpacing, float stackHorizontalPadding, float stackVerticalPadding, uint count, float starInnerScale, VectorData vectorData, StrokeCap strokeCap, StrokeJoin strokeJoin, VectorMirror handleMirroring, ShapeWithTextType shapeWithTextType, ConnectorEndpoint connectorStart, ConnectorEndpoint connectorEnd, StrokeCap connectorStartCap, StrokeCap connectorEndCap, ConnectorLineStyle connectorLineStyle, ConstraintType horizontalConstraint, ConstraintType verticalConstraint, NodeChange[] derivedSymbolData, string key, string version, LeadingTrim leadingTrim, float[] dashPattern, NodePhase phase
message Blob byte[] bytes
message Message NodeChange[] nodeChanges, Blob[] blobs
";
@@ -1589,6 +1590,23 @@ fn library_copies_of_several_versions_become_the_newest() {
),
],
),
// A page section set by a versionless reference, while the file holds several copies.
node(
12,
Some((1, "#")),
"FRAME",
"Versionless",
vec![
("size", xy(100.0, 40.0)),
(
"variableModeBySetMap",
o(vec![(
"entries",
arr(vec![o(vec![("variableSetID", o(vec![("assetRef", o(vec![("key", s("products"))]))])), ("variableModeID", g(92))])]),
)]),
),
],
),
// The button names the older copy of the variable.
node(
10,
@@ -1618,7 +1636,42 @@ fn library_copies_of_several_versions_become_the_newest() {
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);
assert_eq!(named(&doc, "Versionless").var_modes.get(&products.id).map(String::as_str), Some("1:92"));
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:?}");
}
/// A copy of a library collection that a later change removed, followed by a live node for the
/// same asset key and version: the live one is the collection, not the removed one the asset
/// index met first.
#[test]
fn removed_library_collection_copies_give_way_to_live_ones() {
let mode = |id, name: &'static str, pos: &'static str| o(vec![("id", g(id)), ("name", s(name)), ("sortPosition", s(pos))]);
let by_asset = |version: &'static str| o(vec![("assetRef", o(vec![("key", s("products")), ("version", s(version))]))]);
let set = |id, version: &'static str, modes: Vec<Kv<'static>>| {
node(id, Some((2, "!")), "VARIABLE_SET", "Products", vec![("variableSetModes", arr(modes)), ("key", s("products")), ("version", s(version))])
};
let removed = o(vec![("guid", g(8)), ("phase", e("REMOVED"))]);
let nodes = vec![
node(0, None, "DOCUMENT", "Document", vec![]),
node(
1,
Some((0, "!")),
"CANVAS",
"Page",
vec![("variableModeBySetMap", o(vec![("entries", arr(vec![o(vec![("variableSetID", by_asset("30:1")), ("variableModeID", g(92))])]))]))],
),
node(2, Some((0, "~")), "CANVAS", "Internal", vec![("internalOnly", Kv::Bool(true))]),
set(4, "10:1", vec![mode(90, "Blue", "!")]),
set(8, "30:1", vec![mode(90, "Blue", "!"), mode(92, "Purple", "\"")]),
removed,
set(9, "30:1", vec![mode(90, "Blue", "!"), mode(92, "Purple", "\"")]),
];
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.id, "1:9");
let page = doc.nodes().find(|n| n.name == "Page").unwrap();
assert_eq!(page.var_modes.get("1:9").map(String::as_str), Some("1:92"), "{warnings:?}");
}