@toghen/figma 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +18 -2
- package/cli/bin.mjs +19 -0
- package/cli/bridge.ts +243 -0
- package/cli/check.ts +62 -0
- package/cli/figcraft.ts +764 -0
- package/cli/figcraftApply.ts +1067 -0
- package/cli/figcraftFile.ts +476 -0
- package/cli/figcraftLibrary.ts +147 -0
- package/cli/figcraftRecord.ts +121 -0
- package/cli/figcraftScene.ts +661 -0
- package/cli/main.ts +218 -0
- package/cli/members.ts +65 -0
- package/cli/run.ts +756 -0
- package/component-files.md +665 -0
- package/dist/code.js +24161 -0
- package/dist/manifest.json +23 -0
- package/dist/ui.html +6918 -0
- package/guide.md +985 -0
- package/main/code.ts +50 -0
- package/main/components/apply.ts +704 -0
- package/main/components/endOfRun.ts +168 -0
- package/main/components/errors.ts +17 -0
- package/main/components/fieldWriter.ts +497 -0
- package/main/components/file.ts +263 -0
- package/main/components/receipt.ts +228 -0
- package/main/components/resolve.ts +1193 -0
- package/main/components/run.ts +273 -0
- package/main/components/snapshot.ts +341 -0
- package/main/components/stamp.ts +312 -0
- package/main/components/update.ts +465 -0
- package/main/dispatcher.ts +33 -0
- package/main/engines/batchRunner.ts +200 -0
- package/main/engines/ioAdapter.ts +132 -0
- package/main/engines/operationExecutor.ts +512 -0
- package/main/engines/propertyMapper.ts +221 -0
- package/main/engines/queryEngine.ts +45 -0
- package/main/engines/styleSyncEngine.ts +444 -0
- package/main/engines/variableSyncEngine.ts +561 -0
- package/main/handlers/auditSelection.ts +60 -0
- package/main/handlers/boundLayers.ts +51 -0
- package/main/handlers/cancelOperation.ts +18 -0
- package/main/handlers/executeApplyBindings.ts +69 -0
- package/main/handlers/executeScaffold.ts +59 -0
- package/main/handlers/executeStyleSync.ts +121 -0
- package/main/handlers/executeVariableSync.ts +82 -0
- package/main/handlers/exportDocument.ts +114 -0
- package/main/handlers/getSelection.ts +43 -0
- package/main/handlers/index.ts +30 -0
- package/main/handlers/inspectCorrespondence.ts +172 -0
- package/main/handlers/loadConventions.ts +86 -0
- package/main/handlers/loadDocument.ts +253 -0
- package/main/handlers/lock.ts +270 -0
- package/main/handlers/planApply.ts +157 -0
- package/main/handlers/planApplyBindings.ts +79 -0
- package/main/handlers/planScaffold.ts +84 -0
- package/main/handlers/planStyleSync.ts +49 -0
- package/main/handlers/planVariableSync.ts +49 -0
- package/main/handlers/readSelectionBindings.ts +160 -0
- package/main/handlers/resizeUi.ts +105 -0
- package/main/handlers/runComponent.ts +65 -0
- package/main/handlers/templates.ts +462 -0
- package/main/handlers/tokenGraph.ts +122 -0
- package/main/runtimeState.ts +66 -0
- package/main/utils/abortController.ts +11 -0
- package/main/utils/boundVariables.ts +84 -0
- package/main/utils/buildPayloads.ts +85 -0
- package/main/utils/correspondence.ts +658 -0
- package/main/utils/designSource.ts +122 -0
- package/main/utils/figmaCollectionIndex.ts +120 -0
- package/main/utils/figmaStyleAdapter.ts +268 -0
- package/main/utils/figmaTokenAdapter.ts +255 -0
- package/main/utils/flattenDesignSystem.ts +38 -0
- package/main/utils/folds.ts +203 -0
- package/main/utils/fontStack.ts +57 -0
- package/main/utils/lists.ts +4 -0
- package/main/utils/lockStore.ts +72 -0
- package/main/utils/moves.ts +67 -0
- package/main/utils/pageRoots.ts +58 -0
- package/main/utils/planSymbols.ts +96 -0
- package/main/utils/purpose.ts +351 -0
- package/main/utils/recentStore.ts +166 -0
- package/main/utils/resolverModes.ts +271 -0
- package/main/utils/rootRecord.ts +137 -0
- package/main/utils/shardedData.ts +142 -0
- package/main/utils/standing.ts +106 -0
- package/main/utils/styleTarget.ts +51 -0
- package/main/utils/sum.ts +612 -0
- package/main/utils/symbolicIdRegistry.ts +26 -0
- package/main/utils/syncState.ts +133 -0
- package/main/utils/templateStore.ts +73 -0
- package/main/utils/tokenPath.ts +28 -0
- package/main/utils/tokenStamps.ts +91 -0
- package/manifest.json +15 -0
- package/package.json +64 -4
- package/reference.md +1609 -0
- package/shared/components/artboard.ts +126 -0
- package/shared/components/axes.ts +420 -0
- package/shared/components/bindings.ts +64 -0
- package/shared/components/blueprint.ts +248 -0
- package/shared/components/check.ts +705 -0
- package/shared/components/diff.ts +668 -0
- package/shared/components/fields.ts +404 -0
- package/shared/components/groups.ts +306 -0
- package/shared/components/index.ts +140 -0
- package/shared/components/members.ts +280 -0
- package/shared/components/model.ts +273 -0
- package/shared/components/order.ts +53 -0
- package/shared/components/read.ts +480 -0
- package/shared/components/stack.ts +124 -0
- package/shared/components/steps.ts +186 -0
- package/shared/components/validate.ts +565 -0
- package/shared/conventions.ts +28 -0
- package/shared/inputs.ts +40 -0
- package/shared/operations.ts +233 -0
- package/shared/pendingNames.ts +39 -0
- package/shared/protocol.ts +588 -0
- package/shared/tokenTypeMap.ts +21 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What happens once per run, around the component and artboard steps: strays are found before anything is written,
|
|
3
|
+
* and at the end the strays are removed and each page's sets and artboards are stacked again.
|
|
4
|
+
*
|
|
5
|
+
* A run is one `components` or `sync` command, or one template run from the plugin's screen. Its steps arrive one file
|
|
6
|
+
* at a time, so what the run has made or changed is kept here until `finishRun` (`specs.md`, *Strays, the board, and
|
|
7
|
+
* where sets go*). The stack itself is `shared/components/stack.ts`; this is what reads Figma for it and writes back.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describeStack, restack, type TopLevel } from '../../shared/components/stack.js';
|
|
11
|
+
import { findInEveryPage } from '../utils/pageRoots.js';
|
|
12
|
+
import { copiesFinder, keepsNoStamp, outermost, sortNodes, stampedAs, stampOn, strip, updateStamp, type Sorted } from './stamp.js';
|
|
13
|
+
|
|
14
|
+
/** The pages this run made or changed a set or an artboard on, and the nodes it made, in the order it made them. */
|
|
15
|
+
const run = { pages: new Set<string>(), made: [] as string[] };
|
|
16
|
+
|
|
17
|
+
/** A new run: forgets what the one before made, if it never finished. */
|
|
18
|
+
export function beginRun(): void {
|
|
19
|
+
run.pages.clear();
|
|
20
|
+
run.made = [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A set or an artboard was made (`madeId`) or changed on this page. */
|
|
24
|
+
export function noteTouched(page: PageNode, madeId?: string): void {
|
|
25
|
+
run.pages.add(page.id);
|
|
26
|
+
if (madeId !== undefined) run.made.push(madeId);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function pageOf(node: BaseNode): PageNode {
|
|
30
|
+
let up: BaseNode | null = node;
|
|
31
|
+
while (up && up.type !== 'PAGE') up = up.parent;
|
|
32
|
+
if (!up) throw new Error(`the node ${node.id} is not on a page`);
|
|
33
|
+
return up;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// strays
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/** A member outside any set: a component stamped as a member whose parent is not a set stamped as a component. */
|
|
41
|
+
export interface Stray {
|
|
42
|
+
id: string;
|
|
43
|
+
/** Its combination, from its stamp: it names no component. */
|
|
44
|
+
name: string;
|
|
45
|
+
node: ComponentNode;
|
|
46
|
+
/** The person's copies of it and of what is in it, whose stamps its removal strips (decisions, *A copy whose original Toghen removes*). */
|
|
47
|
+
copies: SceneNode[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The members outside any set, Toghen's and a person's copies of a member (`sortNodes`). Every page must be loaded. */
|
|
51
|
+
const loose = (): Promise<Sorted<ComponentNode>> =>
|
|
52
|
+
sortNodes(
|
|
53
|
+
(findInEveryPage({ types: ['COMPONENT'], sharedPluginData: stampedAs('member') }) as ComponentNode[]).filter(
|
|
54
|
+
(node) =>
|
|
55
|
+
stampOn(node)?.is.member !== undefined &&
|
|
56
|
+
!(node.parent?.type === 'COMPONENT_SET' && stampOn(node.parent)?.is.component !== undefined),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
/** Every stray on every page, loading each page first. A person's copy of a member is not one: it is left alone. */
|
|
61
|
+
export async function findStrays(): Promise<Stray[]> {
|
|
62
|
+
for (const page of figma.root.children) await page.loadAsync();
|
|
63
|
+
const strays = (await loose()).ours;
|
|
64
|
+
const copiesOf = strays.length > 0 ? copiesFinder() : undefined;
|
|
65
|
+
return strays.map((node) => ({ id: node.id, name: stampOn(node)?.is.member ?? node.name, node, copies: copiesOf!(node) }));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const andCopies = (stray: Stray): string => (stray.copies.length > 0 ? `; ${keepsNoStamp(outermost(stray.copies))}` : '');
|
|
69
|
+
|
|
70
|
+
/** Always breaking: its instances may be in other files that use this one as a library, which the run cannot see. */
|
|
71
|
+
export const strayLine = (stray: Stray): string =>
|
|
72
|
+
`"${stray.name}" (${stray.id}) is a member outside any set, left by an earlier run; it is removed with --allow-breaking${andCopies(stray)}.`;
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// the end of a run
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
export interface Finished {
|
|
79
|
+
/** One line for each stray removed. */
|
|
80
|
+
removed: string[];
|
|
81
|
+
/** One line for each set or artboard moved. */
|
|
82
|
+
moved: string[];
|
|
83
|
+
/** A set moved by hand that overlaps something; and for a dry run, what each page would restack. */
|
|
84
|
+
notes: string[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const isStamped = (node: SceneNode): boolean =>
|
|
88
|
+
(node.type === 'COMPONENT_SET' && stampOn(node)?.is.component !== undefined) ||
|
|
89
|
+
(node.type === 'FRAME' && stampOn(node)?.is.artboard !== undefined);
|
|
90
|
+
|
|
91
|
+
/** The sets and artboard frames on every page that are a person's copies of Toghen's: they stay where the person put them. */
|
|
92
|
+
async function copiedBoards(): Promise<Set<SceneNode>> {
|
|
93
|
+
const boards = [
|
|
94
|
+
...findInEveryPage({ types: ['COMPONENT_SET'], sharedPluginData: stampedAs('component') }),
|
|
95
|
+
...findInEveryPage({ types: ['FRAME'], sharedPluginData: stampedAs('artboard') }),
|
|
96
|
+
].filter(isStamped);
|
|
97
|
+
return new Set((await sortNodes(boards)).copies.map(({ copy }) => copy));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const topLevel = (page: PageNode, copies: ReadonlySet<SceneNode>): TopLevel[] =>
|
|
101
|
+
page.children.map((node) => {
|
|
102
|
+
const placed = stampOn(node)?.placed;
|
|
103
|
+
const made = run.made.indexOf(node.id);
|
|
104
|
+
return {
|
|
105
|
+
id: node.id,
|
|
106
|
+
name: node.name,
|
|
107
|
+
x: node.x,
|
|
108
|
+
y: node.y,
|
|
109
|
+
width: node.width,
|
|
110
|
+
height: node.height,
|
|
111
|
+
toghen: isStamped(node) && !copies.has(node),
|
|
112
|
+
...(placed ? { placed } : {}),
|
|
113
|
+
...(made !== -1 ? { created: made } : {}),
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Once, after the last component or artboard step of a run. A dry run says what each page would restack and which sets
|
|
119
|
+
* were moved by hand, and writes nothing. A real run removes the strays when it is allowed to, then on each page it
|
|
120
|
+
* touched stacks Toghen's sets and artboards again and writes their `placed` stamps once they are where they go.
|
|
121
|
+
*/
|
|
122
|
+
export async function finishRun(options: { dryRun?: boolean; allowBreaking?: boolean }): Promise<Finished> {
|
|
123
|
+
const finished: Finished = { removed: [], moved: [], notes: [] };
|
|
124
|
+
const pages = figma.root.children.filter((page) => run.pages.has(page.id));
|
|
125
|
+
let copies = new Set<SceneNode>();
|
|
126
|
+
if (pages.length > 0) {
|
|
127
|
+
// Every page: a copy may be on another page than its original.
|
|
128
|
+
for (const page of figma.root.children) await page.loadAsync();
|
|
129
|
+
copies = await copiedBoards();
|
|
130
|
+
// A person's copy of a member, outside any set, is said once a run and left alone.
|
|
131
|
+
for (const { copy, of } of (await loose()).copies)
|
|
132
|
+
finished.notes.push(
|
|
133
|
+
`"${copy.name}" (${copy.id}) is a copy of Toghen's member "${stampOn(of)?.is.member ?? of.name}", outside any set, left alone`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (options.dryRun) {
|
|
138
|
+
for (const page of pages) finished.notes.push(...describeStack(page.name, topLevel(page, copies)));
|
|
139
|
+
} else {
|
|
140
|
+
// Strays first: a loose member at the top level is something the stack would flow around.
|
|
141
|
+
if (options.allowBreaking) {
|
|
142
|
+
for (const stray of await findStrays()) {
|
|
143
|
+
stray.node.remove();
|
|
144
|
+
for (const copy of stray.copies) strip(copy);
|
|
145
|
+
finished.removed.push(`removed "${stray.name}" (${stray.id})${andCopies(stray)}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
for (const page of pages) {
|
|
149
|
+
const plan = restack(topLevel(page, copies));
|
|
150
|
+
const nodes = new Map(page.children.map((node) => [node.id, node]));
|
|
151
|
+
for (const move of plan.moves) {
|
|
152
|
+
const node = nodes.get(move.id) as SceneNode;
|
|
153
|
+
node.x = move.x;
|
|
154
|
+
node.y = move.y;
|
|
155
|
+
finished.moved.push(`moved "${move.name}" to ${move.x}, ${move.y}`);
|
|
156
|
+
}
|
|
157
|
+
// After the moves: a stamp says where the set is.
|
|
158
|
+
for (const at of plan.stamps) {
|
|
159
|
+
const node = nodes.get(at.id) as SceneNode;
|
|
160
|
+
const held = stampOn(node)?.placed;
|
|
161
|
+
if (!held || held.x !== at.x || held.y !== at.y) updateStamp(node, { placed: { x: at.x, y: at.y } });
|
|
162
|
+
}
|
|
163
|
+
finished.notes.push(...plan.notes);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
beginRun();
|
|
167
|
+
return finished;
|
|
168
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one way the component engine stops.
|
|
3
|
+
*
|
|
4
|
+
* A script that throws is rolled back entirely by Figma, which is the guarantee
|
|
5
|
+
* the design leans on: a set is changed whole or not at all. So a name that does
|
|
6
|
+
* not resolve, and a set that must not be touched, are thrown, and thrown with
|
|
7
|
+
* every reason at once so one run is enough to fix a file.
|
|
8
|
+
*/
|
|
9
|
+
export class ComponentRunError extends Error {
|
|
10
|
+
readonly problems: string[];
|
|
11
|
+
|
|
12
|
+
constructor(headline: string, problems: string[]) {
|
|
13
|
+
super(`${headline}\n${problems.map((problem) => ` - ${problem}`).join('\n')}`);
|
|
14
|
+
this.name = 'ComponentRunError';
|
|
15
|
+
this.problems = problems;
|
|
16
|
+
}
|
|
17
|
+
}
|