@clovis500c/figma-bridge 1.13.0 → 1.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/server.js +569 -147
- package/package.json +1 -1
- package/plugin/code.js +418 -72
- package/plugin/ui.html +11 -2
package/package.json
CHANGED
package/plugin/code.js
CHANGED
|
@@ -418,6 +418,304 @@
|
|
|
418
418
|
return { count: out.length, nodes: out };
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
+
// plugin/lib/naming.ts
|
|
422
|
+
var GENERIC_NAME = /^(frame|rectangle|ellipse|group|vector|text|line|polygon|star|component|instance|section|image|auto ?layout|union|subtract|intersect|exclude|boolean|layer|shape|slice|mask|container|wrapper|div|span|box)(\s*\d+)?(\s+copy(\s*\d+)?)?$/i;
|
|
423
|
+
function isGenericName(n) {
|
|
424
|
+
const name = n.name.trim();
|
|
425
|
+
if (!name || GENERIC_NAME.test(name))
|
|
426
|
+
return true;
|
|
427
|
+
if (n.kind === "text" && n.text !== undefined) {
|
|
428
|
+
const t = n.text.trim();
|
|
429
|
+
const a = name.toLowerCase();
|
|
430
|
+
const b = t.toLowerCase();
|
|
431
|
+
return a === b || a.length >= 12 && b.indexOf(a) === 0;
|
|
432
|
+
}
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
var NUMERIC = /^[\s$€£¥+\-−]*[\d][\d\s.,:%/×x+\-−]*[\s$€£¥%kKmMbB]*$/;
|
|
436
|
+
var SCREEN_W = [360, 375, 390, 393, 402, 412, 414, 428, 430, 440];
|
|
437
|
+
function visibleKids(n) {
|
|
438
|
+
return n.children.filter(function(c) {
|
|
439
|
+
return !c.hidden;
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
function maxFont(n) {
|
|
443
|
+
let m = n.kind === "text" && n.fontSize ? n.fontSize : 0;
|
|
444
|
+
for (let i = 0;i < n.children.length; i++) {
|
|
445
|
+
const c = n.children[i];
|
|
446
|
+
if (c && !c.hidden)
|
|
447
|
+
m = Math.max(m, maxFont(c));
|
|
448
|
+
}
|
|
449
|
+
return m;
|
|
450
|
+
}
|
|
451
|
+
function onlyKinds(list, kinds) {
|
|
452
|
+
for (let i = 0;i < list.length; i++) {
|
|
453
|
+
const c = list[i];
|
|
454
|
+
if (c && kinds.indexOf(c.kind) === -1)
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
return list.length > 0;
|
|
458
|
+
}
|
|
459
|
+
function buttonLike(n) {
|
|
460
|
+
const kids = visibleKids(n);
|
|
461
|
+
if (!n.background || n.h > 72 || kids.length === 0 || kids.length > 3)
|
|
462
|
+
return false;
|
|
463
|
+
let texts = 0;
|
|
464
|
+
for (let i = 0;i < kids.length; i++) {
|
|
465
|
+
const c = kids[i];
|
|
466
|
+
if (!c)
|
|
467
|
+
continue;
|
|
468
|
+
if (c.kind === "text") {
|
|
469
|
+
texts++;
|
|
470
|
+
if ((c.text || "").length > 32)
|
|
471
|
+
return false;
|
|
472
|
+
} else if (c.kind !== "vector" && c.kind !== "image")
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
return texts === 1;
|
|
476
|
+
}
|
|
477
|
+
function isScreen(n) {
|
|
478
|
+
return SCREEN_W.indexOf(Math.round(n.w)) !== -1 && n.h >= 640 || n.w >= 1024 && n.h >= 600;
|
|
479
|
+
}
|
|
480
|
+
function largestText(n, parent) {
|
|
481
|
+
const others = parent ? visibleKids(parent).filter(function(c) {
|
|
482
|
+
return c !== n && c.kind === "text";
|
|
483
|
+
}) : [];
|
|
484
|
+
const size = n.fontSize || 14;
|
|
485
|
+
return others.length > 0 && others.every(function(c) {
|
|
486
|
+
return (c.fontSize || 14) < size;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
function covers(n, p) {
|
|
490
|
+
return Math.abs(n.x) <= 1 && Math.abs(n.y) <= 1 && n.w >= p.w - 1 && n.h >= p.h - 1;
|
|
491
|
+
}
|
|
492
|
+
function roleOf(n, parent, root, top, inButton) {
|
|
493
|
+
const kids = visibleKids(n);
|
|
494
|
+
if (n.kind === "text") {
|
|
495
|
+
const t = (n.text || "").trim();
|
|
496
|
+
const size = n.fontSize || 14;
|
|
497
|
+
if (inButton)
|
|
498
|
+
return "Label";
|
|
499
|
+
if (NUMERIC.test(t))
|
|
500
|
+
return "Value";
|
|
501
|
+
if (size >= top - 0.5 && size >= 18)
|
|
502
|
+
return "Title";
|
|
503
|
+
if (size >= 16 && largestText(n, parent))
|
|
504
|
+
return "Title";
|
|
505
|
+
if (size >= 20)
|
|
506
|
+
return "Heading";
|
|
507
|
+
if (t.length > 60)
|
|
508
|
+
return "Description";
|
|
509
|
+
if (size <= 12)
|
|
510
|
+
return "Caption";
|
|
511
|
+
if (t.length > 30)
|
|
512
|
+
return "Description";
|
|
513
|
+
return "Label";
|
|
514
|
+
}
|
|
515
|
+
if (n.kind === "vector")
|
|
516
|
+
return "Icon";
|
|
517
|
+
if (n.kind === "image" && kids.length === 0) {
|
|
518
|
+
const round = n.ellipse || n.radius !== undefined && Math.abs(n.w - n.h) <= 1 && n.radius >= n.w / 2 - 1;
|
|
519
|
+
return round ? "Avatar" : "Image";
|
|
520
|
+
}
|
|
521
|
+
if (kids.length === 0) {
|
|
522
|
+
if (Math.min(n.w, n.h) <= 2 && Math.max(n.w, n.h) > 8)
|
|
523
|
+
return "Divider";
|
|
524
|
+
if (parent && covers(n, parent))
|
|
525
|
+
return "Background";
|
|
526
|
+
if (n.clickable)
|
|
527
|
+
return "Button";
|
|
528
|
+
if (n.ellipse)
|
|
529
|
+
return n.w <= 16 ? "Dot" : "Circle";
|
|
530
|
+
return "Shape";
|
|
531
|
+
}
|
|
532
|
+
if (n.clickable)
|
|
533
|
+
return "Button";
|
|
534
|
+
if (buttonLike(n)) {
|
|
535
|
+
const label = kids.filter(function(c) {
|
|
536
|
+
return c.kind === "text";
|
|
537
|
+
})[0];
|
|
538
|
+
return n.h <= 24 || label && (label.fontSize || 14) <= 12 ? "Badge" : "Button";
|
|
539
|
+
}
|
|
540
|
+
if (!parent) {
|
|
541
|
+
if (isScreen(n))
|
|
542
|
+
return "Screen";
|
|
543
|
+
} else if (parent === root && isScreen(root)) {
|
|
544
|
+
if (n.w >= root.w * 0.9 && n.y <= root.h * 0.1 && n.h <= root.h * 0.25)
|
|
545
|
+
return "Header";
|
|
546
|
+
if (n.w >= root.w * 0.9 && n.y + n.h >= root.h * 0.9 && n.h <= root.h * 0.25)
|
|
547
|
+
return "Footer";
|
|
548
|
+
if (n.h >= root.h * 0.9 && n.w <= root.w * 0.35 && n.x <= 1)
|
|
549
|
+
return "Sidebar";
|
|
550
|
+
}
|
|
551
|
+
const flow = kids.filter(function(c) {
|
|
552
|
+
return !c.absolute;
|
|
553
|
+
});
|
|
554
|
+
if (flow.length >= 2) {
|
|
555
|
+
const r = baseName(flow[0]);
|
|
556
|
+
const same = flow.every(function(c) {
|
|
557
|
+
return baseName(c) === r;
|
|
558
|
+
});
|
|
559
|
+
if (same && r && r !== "Label" && r !== "Value" && r !== "Divider") {
|
|
560
|
+
if (r === "Button")
|
|
561
|
+
return "Actions";
|
|
562
|
+
return r + (n.layout === "GRID" || n.wrap ? "Grid" : "List");
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (n.background)
|
|
566
|
+
return n.radius ? "Card" : "Panel";
|
|
567
|
+
if (onlyKinds(kids, ["text"]))
|
|
568
|
+
return "TextGroup";
|
|
569
|
+
if (n.layout === "HORIZONTAL")
|
|
570
|
+
return "Row";
|
|
571
|
+
if (n.layout === "GRID")
|
|
572
|
+
return "Grid";
|
|
573
|
+
return "Container";
|
|
574
|
+
}
|
|
575
|
+
function baseName(n) {
|
|
576
|
+
return pascalName(n.role || n.name).replace(/\d+$/, "");
|
|
577
|
+
}
|
|
578
|
+
function inferRoles(root) {
|
|
579
|
+
const top = maxFont(root);
|
|
580
|
+
const walk = function(n, parent, inButton) {
|
|
581
|
+
const button = n.kind !== "text" && (n.clickable || buttonLike(n) || /button|btn/i.test(n.name));
|
|
582
|
+
for (let i = 0;i < n.children.length; i++) {
|
|
583
|
+
const c = n.children[i];
|
|
584
|
+
if (c)
|
|
585
|
+
walk(c, n, inButton || button);
|
|
586
|
+
}
|
|
587
|
+
if (isGenericName(n))
|
|
588
|
+
n.role = roleOf(n, parent, root, top, inButton);
|
|
589
|
+
};
|
|
590
|
+
walk(root, null, false);
|
|
591
|
+
}
|
|
592
|
+
function pascalName(name) {
|
|
593
|
+
let segs = name.split("/").map(function(s) {
|
|
594
|
+
return s.replace(/^[\w-]+:/, "").trim();
|
|
595
|
+
}).filter(function(s) {
|
|
596
|
+
return s && !/^default$/i.test(s);
|
|
597
|
+
});
|
|
598
|
+
if (segs.length > 1)
|
|
599
|
+
segs = segs.slice(1).concat([segs[0]]);
|
|
600
|
+
const words = segs.join(" ").normalize("NFKD").replace(/[̀-ͯ]/g, "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
601
|
+
const out = words.map(function(w) {
|
|
602
|
+
return w === w.toUpperCase() && w.length <= 4 ? w : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();
|
|
603
|
+
}).join("");
|
|
604
|
+
return out.slice(0, 48);
|
|
605
|
+
}
|
|
606
|
+
var ROLE_WORDS = ("Actions Avatar Background Badge Banner Bar Body Button Card Column Container Content Controls Details Dialog Divider Footer Frame Grid Group Gui " + "Header Hero Hud HUD Info Item List Menu Modal Nav Navbar Navigation Overlay Panel Popup Row Screen Section Sidebar Slot Stack Stats Tab Tabs Tile " + "Toolbar Tooltip Window Wrapper").split(" ");
|
|
607
|
+
|
|
608
|
+
// plugin/lib/roles.ts
|
|
609
|
+
var VECTORS = ["VECTOR", "BOOLEAN_OPERATION", "STAR", "POLYGON", "LINE"];
|
|
610
|
+
function visiblePaints(list) {
|
|
611
|
+
if (!list || list === figma.mixed)
|
|
612
|
+
return [];
|
|
613
|
+
return list.filter(function(p) {
|
|
614
|
+
return p.visible !== false && (p.opacity === undefined || p.opacity > 0);
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
function vectorOnly(n) {
|
|
618
|
+
if (VECTORS.indexOf(n.type) !== -1)
|
|
619
|
+
return true;
|
|
620
|
+
if (n.type === "TEXT" || !("children" in n) || !n.children.length)
|
|
621
|
+
return false;
|
|
622
|
+
for (let i = 0;i < n.children.length; i++)
|
|
623
|
+
if (n.children[i].visible !== false && !vectorOnly(n.children[i]))
|
|
624
|
+
return false;
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
function fontSizeOf(t) {
|
|
628
|
+
if (typeof t.fontSize === "number")
|
|
629
|
+
return t.fontSize;
|
|
630
|
+
let max = 0;
|
|
631
|
+
try {
|
|
632
|
+
const segs = t.getStyledTextSegments(["fontSize"]);
|
|
633
|
+
for (let i = 0;i < segs.length; i++)
|
|
634
|
+
max = Math.max(max, segs[i].fontSize);
|
|
635
|
+
} catch (e) {}
|
|
636
|
+
return max || 14;
|
|
637
|
+
}
|
|
638
|
+
function describe(n, depth) {
|
|
639
|
+
const fills = "fills" in n ? visiblePaints(n.fills) : [];
|
|
640
|
+
const strokes = "strokes" in n ? visiblePaints(n.strokes) : [];
|
|
641
|
+
const kids = "children" in n && n.type !== "TEXT" && depth < 12 ? n.children : [];
|
|
642
|
+
const image = fills.length === 1 && fills[0].type === "IMAGE";
|
|
643
|
+
let kind = "shape";
|
|
644
|
+
if (n.type === "TEXT")
|
|
645
|
+
kind = "text";
|
|
646
|
+
else if (vectorOnly(n))
|
|
647
|
+
kind = "vector";
|
|
648
|
+
else if (image && !kids.length)
|
|
649
|
+
kind = "image";
|
|
650
|
+
else if (kids.length || n.layoutMode && n.layoutMode !== "NONE")
|
|
651
|
+
kind = "frame";
|
|
652
|
+
let radius = 0;
|
|
653
|
+
if (typeof n.cornerRadius === "number")
|
|
654
|
+
radius = n.cornerRadius;
|
|
655
|
+
else if ("topLeftRadius" in n)
|
|
656
|
+
radius = Math.max(n.topLeftRadius, n.topRightRadius, n.bottomRightRadius, n.bottomLeftRadius);
|
|
657
|
+
const r = {
|
|
658
|
+
node: n,
|
|
659
|
+
kind,
|
|
660
|
+
name: n.name,
|
|
661
|
+
x: n.x || 0,
|
|
662
|
+
y: n.y || 0,
|
|
663
|
+
w: n.width || 0,
|
|
664
|
+
h: n.height || 0,
|
|
665
|
+
hidden: n.visible === false,
|
|
666
|
+
background: fills.length > 0 || strokes.length > 0,
|
|
667
|
+
radius,
|
|
668
|
+
ellipse: n.type === "ELLIPSE",
|
|
669
|
+
clickable: !!(n.reactions && n.reactions.some(function(x) {
|
|
670
|
+
return x.trigger && (x.trigger.type === "ON_CLICK" || x.trigger.type === "ON_PRESS");
|
|
671
|
+
})),
|
|
672
|
+
absolute: n.layoutPositioning === "ABSOLUTE",
|
|
673
|
+
children: []
|
|
674
|
+
};
|
|
675
|
+
if (n.type === "TEXT") {
|
|
676
|
+
r.text = n.characters;
|
|
677
|
+
r.fontSize = fontSizeOf(n);
|
|
678
|
+
}
|
|
679
|
+
if (n.layoutMode && n.layoutMode !== "NONE")
|
|
680
|
+
r.layout = n.layoutMode;
|
|
681
|
+
if (n.layoutWrap === "WRAP")
|
|
682
|
+
r.wrap = true;
|
|
683
|
+
if (kind !== "vector") {
|
|
684
|
+
for (let i = 0;i < kids.length; i++)
|
|
685
|
+
r.children.push(describe(kids[i], depth + 1));
|
|
686
|
+
}
|
|
687
|
+
return r;
|
|
688
|
+
}
|
|
689
|
+
function roleNames(root) {
|
|
690
|
+
const tree = describe(root, 0);
|
|
691
|
+
inferRoles(tree);
|
|
692
|
+
const out = {};
|
|
693
|
+
const walk = function(r) {
|
|
694
|
+
if (r.role)
|
|
695
|
+
out[r.node.id] = r.role;
|
|
696
|
+
for (let i = 0;i < r.children.length; i++)
|
|
697
|
+
walk(r.children[i]);
|
|
698
|
+
};
|
|
699
|
+
walk(tree);
|
|
700
|
+
return out;
|
|
701
|
+
}
|
|
702
|
+
function applyRoleNames(root, rename) {
|
|
703
|
+
const names = roleNames(root);
|
|
704
|
+
let count = 0;
|
|
705
|
+
const walk = function(n) {
|
|
706
|
+
const role = names[n.id];
|
|
707
|
+
if (role && rename(n) && n.name !== role) {
|
|
708
|
+
n.name = role;
|
|
709
|
+
count++;
|
|
710
|
+
}
|
|
711
|
+
if ("children" in n && n.type !== "INSTANCE")
|
|
712
|
+
for (let i = 0;i < n.children.length; i++)
|
|
713
|
+
walk(n.children[i]);
|
|
714
|
+
};
|
|
715
|
+
walk(root);
|
|
716
|
+
return count;
|
|
717
|
+
}
|
|
718
|
+
|
|
421
719
|
// plugin/lib/health.ts
|
|
422
720
|
var MAX_NODES = 20000;
|
|
423
721
|
var MAX_ISSUES = 100;
|
|
@@ -829,6 +1127,15 @@
|
|
|
829
1127
|
}
|
|
830
1128
|
const changes = [];
|
|
831
1129
|
const counts = { colors: 0, numbers: 0, textStyles: 0, names: 0 };
|
|
1130
|
+
const roleCache = {};
|
|
1131
|
+
const roleFor = function(n) {
|
|
1132
|
+
let top = n;
|
|
1133
|
+
while (top.parent && top.parent.type !== "PAGE" && top.parent.type !== "DOCUMENT" && top.parent.type !== "SECTION")
|
|
1134
|
+
top = top.parent;
|
|
1135
|
+
if (!roleCache[top.id])
|
|
1136
|
+
roleCache[top.id] = roleNames(top);
|
|
1137
|
+
return roleCache[top.id][n.id] || null;
|
|
1138
|
+
};
|
|
832
1139
|
const record = function(n, fix, from, to) {
|
|
833
1140
|
counts[fix]++;
|
|
834
1141
|
if (changes.length < 200)
|
|
@@ -935,7 +1242,7 @@
|
|
|
935
1242
|
}
|
|
936
1243
|
}
|
|
937
1244
|
if (fixes.indexOf("names") !== -1 && n.type !== "TEXT" && DEFAULT_NAME.test(n.name)) {
|
|
938
|
-
const name =
|
|
1245
|
+
const name = roleFor(n);
|
|
939
1246
|
if (name && name !== n.name) {
|
|
940
1247
|
const from = n.name;
|
|
941
1248
|
n.name = name;
|
|
@@ -945,52 +1252,6 @@
|
|
|
945
1252
|
}
|
|
946
1253
|
return { scope: scope.label, fixed: counts, changes, truncated: scope.truncated || changes.length >= 200 };
|
|
947
1254
|
}
|
|
948
|
-
function firstText(n, depth) {
|
|
949
|
-
if (n.type === "TEXT")
|
|
950
|
-
return n.characters;
|
|
951
|
-
if (depth > 3 || !("children" in n))
|
|
952
|
-
return "";
|
|
953
|
-
for (let i = 0;i < n.children.length; i++) {
|
|
954
|
-
if (n.children[i].visible === false)
|
|
955
|
-
continue;
|
|
956
|
-
const t = firstText(n.children[i], depth + 1);
|
|
957
|
-
if (t.trim())
|
|
958
|
-
return t;
|
|
959
|
-
}
|
|
960
|
-
return "";
|
|
961
|
-
}
|
|
962
|
-
function hasImage(n) {
|
|
963
|
-
return "fills" in n && n.fills !== figma.mixed && n.fills.some(function(f) {
|
|
964
|
-
return f.type === "IMAGE" && f.visible !== false;
|
|
965
|
-
});
|
|
966
|
-
}
|
|
967
|
-
function vectorOnly(n) {
|
|
968
|
-
if (n.type === "VECTOR" || n.type === "BOOLEAN_OPERATION" || n.type === "STAR" || n.type === "POLYGON")
|
|
969
|
-
return true;
|
|
970
|
-
if (!("children" in n) || !n.children.length || n.type === "TEXT")
|
|
971
|
-
return false;
|
|
972
|
-
for (let i = 0;i < n.children.length; i++)
|
|
973
|
-
if (!vectorOnly(n.children[i]))
|
|
974
|
-
return false;
|
|
975
|
-
return true;
|
|
976
|
-
}
|
|
977
|
-
function nameFromContent(n) {
|
|
978
|
-
if (hasImage(n))
|
|
979
|
-
return "Image";
|
|
980
|
-
if (n.type === "VECTOR" || (n.type === "GROUP" || n.type === "FRAME") && vectorOnly(n))
|
|
981
|
-
return "Icon";
|
|
982
|
-
const text = firstText(n, 0).split(`
|
|
983
|
-
`)[0].trim();
|
|
984
|
-
if (text)
|
|
985
|
-
return text.length > 32 ? text.slice(0, 31) + "…" : text;
|
|
986
|
-
if (n.layoutMode === "HORIZONTAL")
|
|
987
|
-
return "Row";
|
|
988
|
-
if (n.layoutMode === "VERTICAL")
|
|
989
|
-
return "Column";
|
|
990
|
-
if (n.layoutMode === "GRID")
|
|
991
|
-
return "Grid";
|
|
992
|
-
return null;
|
|
993
|
-
}
|
|
994
1255
|
|
|
995
1256
|
// plugin/lib/audit.ts
|
|
996
1257
|
var MAX_NODES2 = 5000;
|
|
@@ -1958,6 +2219,7 @@
|
|
|
1958
2219
|
reactions: [],
|
|
1959
2220
|
connectors: [],
|
|
1960
2221
|
keys: {},
|
|
2222
|
+
unnamed: {},
|
|
1961
2223
|
requestId,
|
|
1962
2224
|
total: countNodes(roots),
|
|
1963
2225
|
lastProgress: 0
|
|
@@ -1981,6 +2243,15 @@
|
|
|
1981
2243
|
}
|
|
1982
2244
|
made.push(node);
|
|
1983
2245
|
}
|
|
2246
|
+
for (let i = 0;i < made.length; i++) {
|
|
2247
|
+
try {
|
|
2248
|
+
applyRoleNames(made[i], function(n) {
|
|
2249
|
+
return !!ctx.unnamed[n.id];
|
|
2250
|
+
});
|
|
2251
|
+
} catch (e) {
|
|
2252
|
+
ctx.warnings.push("Layer names: " + (e.message || e));
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
1984
2255
|
if (ctx.reactions.length)
|
|
1985
2256
|
await applyBuildReactions(ctx.reactions, ctx.ids, ctx.warnings);
|
|
1986
2257
|
const connectors = await createConnectors(ctx);
|
|
@@ -2173,6 +2444,8 @@
|
|
|
2173
2444
|
node.name = String(s.name);
|
|
2174
2445
|
else if (type === "icon")
|
|
2175
2446
|
node.name = "icon/" + s.icon;
|
|
2447
|
+
else if (NAMED_BY_ROLE.indexOf(type) !== -1)
|
|
2448
|
+
ctx.unnamed[node.id] = true;
|
|
2176
2449
|
if (type === "frame" || type === "component")
|
|
2177
2450
|
await setupFrame(node, s, ctx, path);
|
|
2178
2451
|
else if (type === "slide")
|
|
@@ -2241,6 +2514,7 @@
|
|
|
2241
2514
|
}
|
|
2242
2515
|
return node;
|
|
2243
2516
|
}
|
|
2517
|
+
var NAMED_BY_ROLE = ["frame", "component", "text", "rect", "ellipse", "line", "image", "svg"];
|
|
2244
2518
|
var FIXED_LOOK = ["sticky", "shape", "table", "codeblock"];
|
|
2245
2519
|
async function setupFrame(f, s, ctx, path, keepFills) {
|
|
2246
2520
|
if (!keepFills)
|
|
@@ -2325,6 +2599,8 @@
|
|
|
2325
2599
|
if (mode === "GRID") {
|
|
2326
2600
|
if (await setupGrid(f, s, ctx, path))
|
|
2327
2601
|
return;
|
|
2602
|
+
if (Array.isArray(s.gridSize))
|
|
2603
|
+
return placeGridItems(f, s);
|
|
2328
2604
|
mode = "HORIZONTAL";
|
|
2329
2605
|
s = Object.assign({}, s, { wrap: true, rowGap: s.rowGap !== undefined ? s.rowGap : s.gap, gap: s.columnGap !== undefined ? s.columnGap : s.gap });
|
|
2330
2606
|
}
|
|
@@ -2369,7 +2645,28 @@
|
|
|
2369
2645
|
return { type: "FIXED", value: parseFloat(px[1]) };
|
|
2370
2646
|
return fallback === "FLEX" ? { type: "FLEX", value: 1 } : { type: "HUG" };
|
|
2371
2647
|
}
|
|
2648
|
+
var GRID_FALLBACK = `Figma's grid track API failed (a Figma bug: "invalid id"): grids were built as wrapping rows, or at their measured positions for imported pages.`;
|
|
2649
|
+
var gridTracksBroken = false;
|
|
2650
|
+
function tracksReadable(f) {
|
|
2651
|
+
try {
|
|
2652
|
+
const t = f.gridColumnSizes[0];
|
|
2653
|
+
return !!t && typeof t.type === "string";
|
|
2654
|
+
} catch (e) {
|
|
2655
|
+
return false;
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2372
2658
|
async function setupGrid(f, s, ctx, path) {
|
|
2659
|
+
const fallback = function() {
|
|
2660
|
+
gridTracksBroken = true;
|
|
2661
|
+
try {
|
|
2662
|
+
f.layoutMode = "NONE";
|
|
2663
|
+
} catch (e) {}
|
|
2664
|
+
if (ctx.warnings.indexOf(GRID_FALLBACK) === -1)
|
|
2665
|
+
ctx.warnings.push(GRID_FALLBACK);
|
|
2666
|
+
return false;
|
|
2667
|
+
};
|
|
2668
|
+
if (gridTracksBroken)
|
|
2669
|
+
return fallback();
|
|
2373
2670
|
try {
|
|
2374
2671
|
f.layoutMode = "GRID";
|
|
2375
2672
|
} catch (e) {
|
|
@@ -2399,14 +2696,16 @@
|
|
|
2399
2696
|
set("rows", function() {
|
|
2400
2697
|
f.gridRowCount = rowCount;
|
|
2401
2698
|
});
|
|
2402
|
-
|
|
2699
|
+
if (!tracksReadable(f))
|
|
2700
|
+
return fallback();
|
|
2701
|
+
try {
|
|
2403
2702
|
for (let i = 0;i < colCount; i++)
|
|
2404
|
-
applyTrack(f
|
|
2405
|
-
});
|
|
2406
|
-
set("row sizes", function() {
|
|
2703
|
+
applyTrack(f, "gridColumnSizes", i, track(cols ? cols[i] : undefined, fixedW ? "FLEX" : "HUG"));
|
|
2407
2704
|
for (let i = 0;i < rowCount; i++)
|
|
2408
|
-
applyTrack(f
|
|
2409
|
-
})
|
|
2705
|
+
applyTrack(f, "gridRowSizes", i, track(rows ? rows[i] : undefined, fixedH ? "FLEX" : "HUG"));
|
|
2706
|
+
} catch (e) {
|
|
2707
|
+
return fallback();
|
|
2708
|
+
}
|
|
2410
2709
|
if (!fixedW)
|
|
2411
2710
|
set("sizing", function() {
|
|
2412
2711
|
f.layoutSizingHorizontal = "HUG";
|
|
@@ -2429,12 +2728,30 @@
|
|
|
2429
2728
|
await setPadding(f, s.padding);
|
|
2430
2729
|
return true;
|
|
2431
2730
|
}
|
|
2432
|
-
function
|
|
2433
|
-
if (
|
|
2731
|
+
function placeGridItems(f, s) {
|
|
2732
|
+
if (s.w === "hug" || s.w === undefined)
|
|
2733
|
+
s.w = s.gridSize[0];
|
|
2734
|
+
if (s.h === "hug" || s.h === undefined)
|
|
2735
|
+
s.h = s.gridSize[1];
|
|
2736
|
+
const kids = Array.isArray(s.children) ? s.children : [];
|
|
2737
|
+
for (let i = 0;i < kids.length; i++) {
|
|
2738
|
+
const p = kids[i] && kids[i].place;
|
|
2739
|
+
if (!Array.isArray(p))
|
|
2740
|
+
continue;
|
|
2741
|
+
kids[i].x = p[0];
|
|
2742
|
+
kids[i].y = p[1];
|
|
2743
|
+
kids[i].w = p[2];
|
|
2744
|
+
if (kids[i].type !== "text" || kids[i].h !== undefined)
|
|
2745
|
+
kids[i].h = p[3];
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
function applyTrack(f, field, i, spec) {
|
|
2749
|
+
if (!f[field][i])
|
|
2434
2750
|
return;
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2751
|
+
if (f[field][i].type !== spec.type)
|
|
2752
|
+
f[field][i].type = spec.type;
|
|
2753
|
+
if (spec.value !== undefined && spec.type !== "HUG" && f[field][i].value !== spec.value)
|
|
2754
|
+
f[field][i].value = spec.value;
|
|
2438
2755
|
}
|
|
2439
2756
|
function spanOf(s, axis) {
|
|
2440
2757
|
if (!s || typeof s !== "object")
|
|
@@ -2853,10 +3170,13 @@
|
|
|
2853
3170
|
const right = v[1] === undefined ? v[0] : v[1];
|
|
2854
3171
|
const bottom = v[2] === undefined ? v[0] : v[2];
|
|
2855
3172
|
const left = v[3] === undefined ? right : v[3];
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
await setNumber(f, "
|
|
3173
|
+
const clamp = function(x) {
|
|
3174
|
+
return typeof x === "number" ? Math.max(0, x) : x;
|
|
3175
|
+
};
|
|
3176
|
+
await setNumber(f, "paddingTop", clamp(top));
|
|
3177
|
+
await setNumber(f, "paddingRight", clamp(right));
|
|
3178
|
+
await setNumber(f, "paddingBottom", clamp(bottom));
|
|
3179
|
+
await setNumber(f, "paddingLeft", clamp(left));
|
|
2860
3180
|
}
|
|
2861
3181
|
function lineHeight(v) {
|
|
2862
3182
|
if (v === "auto")
|
|
@@ -3077,7 +3397,7 @@
|
|
|
3077
3397
|
}
|
|
3078
3398
|
|
|
3079
3399
|
// plugin/lib/describe.ts
|
|
3080
|
-
async function
|
|
3400
|
+
async function describe2(p) {
|
|
3081
3401
|
let targets;
|
|
3082
3402
|
if (p.nodeId)
|
|
3083
3403
|
targets = [await getNode(p.nodeId)];
|
|
@@ -3922,7 +4242,7 @@
|
|
|
3922
4242
|
|
|
3923
4243
|
// plugin/lib/roblox.ts
|
|
3924
4244
|
var MAX_NODES5 = 2000;
|
|
3925
|
-
var
|
|
4245
|
+
var VECTORS2 = ["VECTOR", "BOOLEAN_OPERATION", "STAR", "POLYGON", "LINE"];
|
|
3926
4246
|
async function robloxTree(p) {
|
|
3927
4247
|
const node = p.nodeId ? await getNode(p.nodeId) : figma.currentPage.selection[0];
|
|
3928
4248
|
if (!node)
|
|
@@ -3959,7 +4279,7 @@
|
|
|
3959
4279
|
return out;
|
|
3960
4280
|
}
|
|
3961
4281
|
function vectorOnly2(n) {
|
|
3962
|
-
if (
|
|
4282
|
+
if (VECTORS2.indexOf(n.type) !== -1)
|
|
3963
4283
|
return true;
|
|
3964
4284
|
if (n.type === "TEXT" || !("children" in n) || !n.children.length)
|
|
3965
4285
|
return false;
|
|
@@ -4040,7 +4360,13 @@
|
|
|
4040
4360
|
const image = figma.getImageByHash(h);
|
|
4041
4361
|
if (!image)
|
|
4042
4362
|
continue;
|
|
4043
|
-
|
|
4363
|
+
let bytes;
|
|
4364
|
+
try {
|
|
4365
|
+
bytes = await image.getBytesAsync();
|
|
4366
|
+
} catch (e) {
|
|
4367
|
+
out.rasterize = true;
|
|
4368
|
+
continue;
|
|
4369
|
+
}
|
|
4044
4370
|
if (ctx.imageBytes + bytes.length > 40 << 20)
|
|
4045
4371
|
continue;
|
|
4046
4372
|
ctx.imageBytes += bytes.length;
|
|
@@ -4112,22 +4438,39 @@
|
|
|
4112
4438
|
})
|
|
4113
4439
|
};
|
|
4114
4440
|
}
|
|
4441
|
+
function withTimeout(p, ms, message) {
|
|
4442
|
+
return new Promise(function(resolve, reject) {
|
|
4443
|
+
const timer = setTimeout(function() {
|
|
4444
|
+
reject(new Error(message));
|
|
4445
|
+
}, ms);
|
|
4446
|
+
p.then(function(v) {
|
|
4447
|
+
clearTimeout(timer);
|
|
4448
|
+
resolve(v);
|
|
4449
|
+
}, function(e) {
|
|
4450
|
+
clearTimeout(timer);
|
|
4451
|
+
reject(e);
|
|
4452
|
+
});
|
|
4453
|
+
});
|
|
4454
|
+
}
|
|
4115
4455
|
async function robloxImages(p) {
|
|
4116
4456
|
const items = Array.isArray(p.items) ? p.items : [];
|
|
4117
4457
|
const out = [];
|
|
4118
4458
|
for (let i = 0;i < items.length; i++) {
|
|
4119
4459
|
const it = items[i];
|
|
4120
|
-
|
|
4460
|
+
let scale = Math.max(1, Math.min(4, Number(it.scale) || 2));
|
|
4121
4461
|
let temp = null;
|
|
4122
4462
|
try {
|
|
4123
4463
|
let node = await getNode(it.id);
|
|
4124
4464
|
if (it.mode === "panel" || it.mode === "shadow") {
|
|
4125
4465
|
temp = node.clone();
|
|
4466
|
+
figma.currentPage.appendChild(temp);
|
|
4126
4467
|
if ("children" in temp)
|
|
4127
4468
|
for (let k = temp.children.length - 1;k >= 0; k--)
|
|
4128
4469
|
temp.children[k].remove();
|
|
4129
4470
|
if (it.mode === "panel")
|
|
4130
|
-
temp.effects =
|
|
4471
|
+
temp.effects = node.effects.filter(function(e) {
|
|
4472
|
+
return e.type !== "DROP_SHADOW";
|
|
4473
|
+
});
|
|
4131
4474
|
else {
|
|
4132
4475
|
temp.effects = node.effects.filter(function(e) {
|
|
4133
4476
|
return e.type === "DROP_SHADOW" && e.visible !== false;
|
|
@@ -4136,7 +4479,10 @@
|
|
|
4136
4479
|
}
|
|
4137
4480
|
node = temp;
|
|
4138
4481
|
}
|
|
4139
|
-
const
|
|
4482
|
+
const box = node.absoluteRenderBounds || node.absoluteBoundingBox;
|
|
4483
|
+
if (box)
|
|
4484
|
+
scale = Math.min(scale, Math.floor(1024 / Math.max(1, box.width, box.height) * 1000) / 1000);
|
|
4485
|
+
const bytes = await withTimeout(node.exportAsync({ format: "PNG", constraint: { type: "SCALE", value: scale } }), 20000, "export timed out");
|
|
4140
4486
|
const rb = node.absoluteRenderBounds || node.absoluteBoundingBox;
|
|
4141
4487
|
const bb = node.absoluteBoundingBox;
|
|
4142
4488
|
out.push({
|
|
@@ -4226,7 +4572,7 @@
|
|
|
4226
4572
|
finish(id, codeError("The user cancelled the selection request", "CANCELLED"));
|
|
4227
4573
|
}
|
|
4228
4574
|
// package.json
|
|
4229
|
-
var version = "1.
|
|
4575
|
+
var version = "1.14.2";
|
|
4230
4576
|
|
|
4231
4577
|
// plugin/code.ts
|
|
4232
4578
|
var DEFAULT_SIZE = { width: 340, height: 540 };
|
|
@@ -4340,7 +4686,7 @@
|
|
|
4340
4686
|
var HANDLERS = {
|
|
4341
4687
|
run_script: runScript,
|
|
4342
4688
|
build,
|
|
4343
|
-
describe,
|
|
4689
|
+
describe: describe2,
|
|
4344
4690
|
find: find2,
|
|
4345
4691
|
get_design_system: getDesignSystem,
|
|
4346
4692
|
design_tokens: designTokens,
|
|
@@ -4591,7 +4937,7 @@
|
|
|
4591
4937
|
},
|
|
4592
4938
|
describe: async function(node, depth) {
|
|
4593
4939
|
const id = node && typeof node === "object" ? node.id : node;
|
|
4594
|
-
const out = await
|
|
4940
|
+
const out = await describe2({ nodeId: id, depth });
|
|
4595
4941
|
return out.outline;
|
|
4596
4942
|
}
|
|
4597
4943
|
};
|
package/plugin/ui.html
CHANGED
|
@@ -523,7 +523,8 @@ const partials = new Map();
|
|
|
523
523
|
function encode(msg) {
|
|
524
524
|
const text = JSON.stringify(msg);
|
|
525
525
|
if (text.length <= CHUNK) return [text];
|
|
526
|
-
|
|
526
|
+
// Not crypto.randomUUID: the plugin iframe is not a secure context, where it is undefined.
|
|
527
|
+
const cid = Date.now().toString(36) + Math.random().toString(36).slice(2);
|
|
527
528
|
const n = Math.ceil(text.length / CHUNK);
|
|
528
529
|
const frames = [];
|
|
529
530
|
for (let i = 0; i < n; i++) frames.push(JSON.stringify({ t: "chunk", cid, i, n, d: text.slice(i * CHUNK, (i + 1) * CHUNK) }));
|
|
@@ -541,7 +542,15 @@ function decode(raw) {
|
|
|
541
542
|
}
|
|
542
543
|
function send(msg) {
|
|
543
544
|
if (!ws || ws.readyState !== WebSocket.OPEN) return false;
|
|
544
|
-
|
|
545
|
+
let frames;
|
|
546
|
+
try {
|
|
547
|
+
frames = encode(msg);
|
|
548
|
+
} catch (e) {
|
|
549
|
+
// A reply that can't be encoded still answers the request, so the agent doesn't wait for a timeout.
|
|
550
|
+
if (msg.t !== "res") return false;
|
|
551
|
+
frames = encode({ t: "res", id: msg.id, ok: false, error: "Could not send the result: " + ((e && e.message) || e), code: "PLUGIN_ERROR" });
|
|
552
|
+
}
|
|
553
|
+
for (const f of frames) ws.send(f);
|
|
545
554
|
return true;
|
|
546
555
|
}
|
|
547
556
|
|