diff --git a/CHANGELOG.md b/CHANGELOG.md index 246d2c0..0a00843 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +# Code Connect v1.3.0 (28th January 2025) + +## Features +- Add support for JSX Figma connection files. +- Added an option to automatically create or append the access token to the project's .env file +- Add better handling of many figma components in the wizard (grouping per page) +- Allow variant restrictions to use boolean-like properties + +### General +- Added support for Bitbucket, Gitlab and Azure DevOps for generated source file URLs + +## Fixed +- Don't show a red-cross when the file-matching prompt is finished in the wizard +- Add default values for `@FigmaEnum` declarations in SwiftUI + +### SwiftUI +- Fixed a formatting error when running the CLI + +### React +- Fix nested objects and arrays in props not rendering properly in code snippets +- Fixed a type issue when passing functions as values to `figma.boolean` +- Add support for multiple exports per file in the wizard + +### Storybook +- Add support for different props per example (fixes https://github.com/figma/code-connect/issues/143) +- Add support for `links` and `imports` (fixes https://github.com/figma/code-connect/issues/142) + # Code Connect v1.2.4 (5th December 2024) ## Fixed diff --git a/Package.resolved b/Package.resolved index 00ea305..c3fd205 100644 --- a/Package.resolved +++ b/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/nicklockwood/SwiftFormat", "state" : { - "revision" : "9e5d0d588ab6e271fe9887ec3dde21d544d4b080", - "version" : "0.53.5" + "revision" : "2d5a2b6bde636c1feae2c852ab9a50f221e98c66", + "version" : "0.55.3" } } ], diff --git a/Package.swift b/Package.swift index b7a16f5..e4e88c9 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/apple/swift-syntax", "510.0.3"..."600.0.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"), - .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.49.0"), + .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.55.3"), ], targets: [ .target( diff --git a/cli/package.json b/cli/package.json index 16b4b17..a86d214 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@figma/code-connect", - "version": "1.2.4", + "version": "1.3.0", "description": "A tool for connecting your design system components in code with your design system in Figma", "keywords": [], "author": "Figma", @@ -29,7 +29,8 @@ "figma": "bin/figma" }, "files": [ - "dist/**/*" + "dist/**/*", + "patches/**/*" ], "engines": { "node": ">=18" @@ -40,7 +41,7 @@ "build:web": "pnpm build", "build:webpack": "cross-env NODE_OPTIONS=\"--max-old-space-size=4096\" webpack --mode production", "test": "npm run test:no-coverage -- --coverage", - "test:no-coverage": "cross-env NODE_OPTIONS=--experimental-vm-modules npx jest", + "test:no-coverage": "cross-env NODE_OPTIONS=\"--experimental-vm-modules --no-deprecation\" npx jest", "test:fast": "npm run test -- --testPathIgnorePatterns=template_rendering.test.ts --testPathIgnorePatterns=e2e_parse_command_swift.test.ts --testPathIgnorePatterns=e2e_wizard_swift.test.ts", "test:ci": "npm run test:non-mac -- --runInBand", "test:wizard": "npm run test -- --runInBand --testPathPattern=e2e_wizard_react.test.ts --testPathPattern=e2e_wizard_swift.test.ts", @@ -58,7 +59,8 @@ "bundle:cli:win": "npm run bundle:cli -- -o bundle-cli/figma-win --target node18-win-x64", "publish:npm": "npm install && npm run build && npm run bundle:npm-readme:prepare && npm publish --access public; npm run bundle:npm-readme:restore", "typecheck": "tsc --noEmit -p tsconfig-typecheck.json", - "benchmarking:run": "npx tsx ./src/connect/wizard/__test__/prop_mapping/prop_mapping_benchmarking.ts" + "benchmarking:run": "npx tsx ./src/connect/wizard/__test__/prop_mapping/prop_mapping_benchmarking.ts", + "postinstall": "patch-package" }, "devDependencies": { @@ -66,7 +68,7 @@ "@types/jest": "^29.5.13", "@types/jsdom": "^21.1.7", "@types/lodash": "^4.17.0", - "@types/node": "^20.14.0", + "@types/node": "^22.10.0", "@types/prettier": "2.7.3", "@types/prompts": "^2.4.9", "@types/react": "18.0.26", @@ -86,7 +88,7 @@ "@babel/parser": "7.26.0", "@babel/types": "7.26.0", - "@storybook/csf-tools": "^7.6.7", + "@storybook/csf-tools": "^8.4.7", "boxen": "5.1.1", "chalk": "^4.1.2", "commander": "^11.1.0", @@ -101,6 +103,7 @@ "minimatch": "^9.0.3", "ora": "^5.4.1", "parse5": "^7.1.2", + "patch-package": "^8.0.0", "prettier": "^2.8.8", "prompts": "^2.4.2", "strip-ansi": "^6.0.0", diff --git a/cli/patches/@types+prompts+2.4.9.patch b/cli/patches/@types+prompts+2.4.9.patch new file mode 100644 index 0000000..4423db7 --- /dev/null +++ b/cli/patches/@types+prompts+2.4.9.patch @@ -0,0 +1,12 @@ +diff --git a/node_modules/@types/prompts/index.d.ts b/node_modules/@types/prompts/index.d.ts +index e3124b0..f4fac10 100644 +--- a/node_modules/@types/prompts/index.d.ts ++++ b/node_modules/@types/prompts/index.d.ts +@@ -91,6 +91,7 @@ declare namespace prompts { + mask?: string | PrevCaller | undefined; + stdout?: Writable | undefined; + stdin?: Readable | undefined; ++ submitOnEscapeKey?: boolean | undefined; + } + + type Answers = { [id in T]: any }; diff --git a/cli/patches/prompts+2.4.2.patch b/cli/patches/prompts+2.4.2.patch new file mode 100644 index 0000000..c52be00 --- /dev/null +++ b/cli/patches/prompts+2.4.2.patch @@ -0,0 +1,14 @@ +diff --git a/node_modules/prompts/lib/elements/prompt.js b/node_modules/prompts/lib/elements/prompt.js +index b793330..836de28 100644 +--- a/node_modules/prompts/lib/elements/prompt.js ++++ b/node_modules/prompts/lib/elements/prompt.js +@@ -26,6 +26,9 @@ class Prompt extends EventEmitter { + const isSelect = [ 'SelectPrompt', 'MultiselectPrompt' ].indexOf(this.constructor.name) > -1; + const keypress = (str, key) => { + let a = action(key, isSelect); ++ if (key.name === 'escape' && a === 'exit' && opts.submitOnEscapeKey) { ++ a = 'submit' ++ } + if (a === false) { + this._ && this._(str, key); + } else if (typeof this[a] === 'function') { diff --git a/cli/src/__test__/utils.ts b/cli/src/__test__/utils.ts index f25c13b..5918b46 100644 --- a/cli/src/__test__/utils.ts +++ b/cli/src/__test__/utils.ts @@ -21,3 +21,13 @@ export function tidyStdOutput(input: string): string { .join('\n') ) } + +/** + * Utility function to create a regex that matches a file in the repository. + * This is necessary because the repository URLs (repository name, default branch) get changed + * when publishing to different repositories + */ + +export function getFileInRepositoryRegex(filepath: string): RegExp { + return new RegExp(`https://github.com/figma/code-connect/blob/[a-zA-Z/-]+${filepath}`) +} diff --git a/cli/src/commands/connect.ts b/cli/src/commands/connect.ts index 5ff8454..9831a7d 100644 --- a/cli/src/commands/connect.ts +++ b/cli/src/commands/connect.ts @@ -298,7 +298,7 @@ export async function getCodeConnectObjects( type GetCodeConnectObjectsArgs = { parseFn: ParseFn resolveImportsFn?: ResolveImportsFn - fileExtension: string + fileExtension: string | string[] projectInfo: ProjectInfo cmd: BaseCommand silent?: boolean @@ -381,7 +381,7 @@ async function getReactCodeConnectObjects( const codeConnectObjects = await getCodeConnectObjectsFromParseFn({ parseFn: parseReactDoc, resolveImportsFn: findAndResolveImports, - fileExtension: 'tsx', + fileExtension: ['tsx', 'jsx'], projectInfo, cmd, silent, diff --git a/cli/src/connect/__test__/e2e/e2e_parse_command/dummy_api_response_for_wizard_many_components.json b/cli/src/connect/__test__/e2e/e2e_parse_command/dummy_api_response_for_wizard_many_components.json new file mode 100644 index 0000000..0b9fe4e --- /dev/null +++ b/cli/src/connect/__test__/e2e/e2e_parse_command/dummy_api_response_for_wizard_many_components.json @@ -0,0 +1 @@ +{"document":{"id":"0:0","name":"Document","type":"DOCUMENT","scrollBehavior":"SCROLLS","children":[{"id":"0:1","name":"First Page","type":"CANVAS","scrollBehavior":"SCROLLS","children":[{"id":"1:3","name":"PrimaryButton","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:2","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-436,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":-436,"y":-430,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-436,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":-436,"y":-430,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:203","name":"Component 21","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:191","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":161,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":161,"y":-430,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":161,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":161,"y":-430,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:201","name":"Component 11","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:193","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-140,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":-140,"y":-430,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-140,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":-140,"y":-430,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:205","name":"Component 22","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:195","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":457,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":457,"y":-430,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":457,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":457,"y":-430,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:207","name":"Component 3","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:109","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-436,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":-436,"y":-325,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-436,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":-436,"y":-325,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:229","name":"Component 23","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:113","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":161,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":161,"y":-325,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":161,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":161,"y":-325,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:217","name":"Component 12","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:115","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-140,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":-140,"y":-325,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-140,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":-140,"y":-325,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:245","name":"Component 24","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:119","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":457,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":457,"y":-325,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":457,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":457,"y":-325,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:211","name":"Component 5","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:123","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-436,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":-436,"y":-10,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-436,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":-436,"y":-10,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:210","name":"Component 41","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:135","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-436,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":-436,"y":95,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-436,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":-436,"y":95,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:228","name":"Component 25","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:111","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":161,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":161,"y":-10,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":161,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":161,"y":-10,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:227","name":"Component 42","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:143","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":161,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":161,"y":95,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":161,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":161,"y":95,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:220","name":"Component 13","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:147","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-140,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":-140,"y":-10,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-140,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":-140,"y":-10,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:219","name":"Component 43","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:131","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-140,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":-140,"y":95,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-140,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":-140,"y":95,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:244","name":"Component 26","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:139","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":457,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":457,"y":-10,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":457,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":457,"y":-10,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:243","name":"Component 44","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:127","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":457,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":457,"y":95,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":457,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":457,"y":95,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:209","name":"Component 7","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:149","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-436,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":-436,"y":-115,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-436,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":-436,"y":-115,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:246","name":"Component 27","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:117","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":161,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":161,"y":-115,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":161,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":161,"y":-115,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:221","name":"Component 14","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:151","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-140,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":-140,"y":-115,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-140,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":-140,"y":-115,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:242","name":"Component 28","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:155","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":457,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":457,"y":-115,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":457,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":457,"y":-115,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:208","name":"Component 9","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:157","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-436,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":-436,"y":-220,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-436,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":-436,"y":-220,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:241","name":"Component 29","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:153","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":161,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":161,"y":-220,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":161,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":161,"y":-220,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:218","name":"Component 15","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:141","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-140,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":-140,"y":-220,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-140,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":-140,"y":-220,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:240","name":"Component 30","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:161","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":457,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":457,"y":-220,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":457,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":457,"y":-220,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:10","name":"Component 2","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:9","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-288,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":-288,"y":-430,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-288,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":-288,"y":-430,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:204","name":"Component 31","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:125","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":309,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":309,"y":-430,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":309,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":309,"y":-430,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:202","name":"Component 16","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:197","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":8,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":8,"y":-430,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":8,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":8,"y":-430,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:206","name":"Component 32","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:199","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":605,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":605,"y":-430,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":605,"y":-430,"width":138,"height":88},"absoluteRenderBounds":{"x":605,"y":-430,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:216","name":"Component 4","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:163","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-288,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":-288,"y":-325,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-288,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":-288,"y":-325,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:239","name":"Component 33","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:129","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":309,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":309,"y":-325,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":309,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":309,"y":-325,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:222","name":"Component 17","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:133","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":8,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":8,"y":-325,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":8,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":8,"y":-325,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:238","name":"Component 34","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:165","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":605,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":605,"y":-325,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":605,"y":-325,"width":138,"height":88},"absoluteRenderBounds":{"x":605,"y":-325,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:213","name":"Component 6","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:121","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-288,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":-288,"y":-10,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-288,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":-288,"y":-10,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:212","name":"Component 45","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:159","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-288,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":-288,"y":95,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-288,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":-288,"y":95,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:237","name":"Component 35","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:167","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":309,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":309,"y":-10,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":309,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":309,"y":-10,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:236","name":"Component 46","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:169","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":309,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":309,"y":95,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":309,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":309,"y":95,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:225","name":"Component 18","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:171","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":8,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":8,"y":-10,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":8,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":8,"y":-10,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:226","name":"Component 47","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:173","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":8,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":8,"y":95,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":8,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":8,"y":95,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:235","name":"Component 36","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:175","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":605,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":605,"y":-10,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":605,"y":-10,"width":138,"height":88},"absoluteRenderBounds":{"x":605,"y":-10,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:234","name":"Component 48","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:177","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":605,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":605,"y":95,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":605,"y":95,"width":138,"height":88},"absoluteRenderBounds":{"x":605,"y":95,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:214","name":"Component 8","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:145","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-288,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":-288,"y":-115,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-288,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":-288,"y":-115,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:233","name":"Component 37","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:179","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":309,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":309,"y":-115,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":309,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":309,"y":-115,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:224","name":"Component 19","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:137","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":8,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":8,"y":-115,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":8,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":8,"y":-115,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:232","name":"Component 38","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:181","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":605,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":605,"y":-115,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":605,"y":-115,"width":138,"height":88},"absoluteRenderBounds":{"x":605,"y":-115,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:215","name":"Component 10","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:183","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-288,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":-288,"y":-220,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-288,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":-288,"y":-220,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:231","name":"Component 39","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:185","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":309,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":309,"y":-220,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":309,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":309,"y":-220,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:223","name":"Component 20","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:187","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":8,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":8,"y":-220,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":8,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":8,"y":-220,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:230","name":"Component 40","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:189","name":"Rectangle 1","type":"RECTANGLE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.8509804010391235,"g":0.8509804010391235,"b":0.8509804010391235,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":605,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":605,"y":-220,"width":138,"height":88},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":605,"y":-220,"width":138,"height":88},"absoluteRenderBounds":{"x":605,"y":-220,"width":138,"height":88},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]}],"backgroundColor":{"r":0.9607843160629272,"g":0.9607843160629272,"b":0.9607843160629272,"a":1},"prototypeStartNodeID":null,"flowStartingPoints":[],"prototypeDevice":{"type":"NONE","rotation":"NONE"}},{"id":"1:247","name":"Second Page","type":"CANVAS","scrollBehavior":"SCROLLS","children":[{"id":"1:348","name":"Ellipse 1","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:249","name":"Ellipse 1","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-460,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":-460,"y":-377,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-460,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":-460,"y":-377,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:347","name":"Ellipse 11","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:259","name":"Ellipse 11","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-460,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":-460,"y":-286,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-460,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":-460,"y":-286,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:346","name":"Ellipse 21","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:269","name":"Ellipse 21","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-460,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":-460,"y":-195,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-460,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":-460,"y":-195,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:345","name":"Ellipse 31","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:279","name":"Ellipse 31","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-460,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":-460,"y":-104,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-460,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":-460,"y":-104,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:344","name":"Ellipse 41","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:289","name":"Ellipse 41","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-460,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":-460,"y":-13,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-460,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":-460,"y":-13,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:343","name":"Ellipse 2","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:250","name":"Ellipse 2","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-361,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":-361,"y":-377,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-361,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":-361,"y":-377,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:342","name":"Ellipse 12","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:260","name":"Ellipse 12","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-361,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":-361,"y":-286,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-361,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":-361,"y":-286,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:341","name":"Ellipse 22","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:270","name":"Ellipse 22","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-361,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":-361,"y":-195,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-361,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":-361,"y":-195,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:340","name":"Ellipse 32","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:280","name":"Ellipse 32","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-361,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":-361,"y":-104,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-361,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":-361,"y":-104,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:339","name":"Ellipse 42","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:290","name":"Ellipse 42","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-361,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":-361,"y":-13,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-361,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":-361,"y":-13,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:338","name":"Ellipse 3","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:251","name":"Ellipse 3","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-262,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":-262,"y":-377,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-262,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":-262,"y":-377,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:337","name":"Ellipse 13","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:261","name":"Ellipse 13","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-262,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":-262,"y":-286,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-262,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":-262,"y":-286,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:336","name":"Ellipse 23","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:271","name":"Ellipse 23","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-262,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":-262,"y":-195,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-262,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":-262,"y":-195,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:335","name":"Ellipse 33","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:281","name":"Ellipse 33","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-262,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":-262,"y":-104,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-262,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":-262,"y":-104,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:334","name":"Ellipse 43","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:291","name":"Ellipse 43","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-262,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":-262,"y":-13,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-262,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":-262,"y":-13,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:333","name":"Ellipse 4","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:252","name":"Ellipse 4","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-163,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":-163,"y":-377,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-163,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":-163,"y":-377,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:332","name":"Ellipse 14","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:262","name":"Ellipse 14","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-163,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":-163,"y":-286,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-163,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":-163,"y":-286,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:331","name":"Ellipse 24","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:272","name":"Ellipse 24","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-163,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":-163,"y":-195,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-163,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":-163,"y":-195,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:330","name":"Ellipse 34","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:282","name":"Ellipse 34","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-163,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":-163,"y":-104,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-163,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":-163,"y":-104,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:329","name":"Ellipse 44","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:292","name":"Ellipse 44","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-163,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":-163,"y":-13,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-163,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":-163,"y":-13,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:328","name":"Ellipse 5","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:253","name":"Ellipse 5","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-64,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":-64,"y":-377,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-64,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":-64,"y":-377,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:327","name":"Ellipse 15","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:263","name":"Ellipse 15","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-64,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":-64,"y":-286,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-64,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":-64,"y":-286,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:326","name":"Ellipse 25","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:273","name":"Ellipse 25","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-64,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":-64,"y":-195,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-64,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":-64,"y":-195,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:325","name":"Ellipse 35","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:283","name":"Ellipse 35","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-64,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":-64,"y":-104,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-64,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":-64,"y":-104,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:324","name":"Ellipse 45","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:293","name":"Ellipse 45","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":-64,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":-64,"y":-13,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":-64,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":-64,"y":-13,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:323","name":"Ellipse 6","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:254","name":"Ellipse 6","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":35,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":35,"y":-377,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":35,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":35,"y":-377,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:322","name":"Ellipse 16","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:264","name":"Ellipse 16","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":35,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":35,"y":-286,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":35,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":35,"y":-286,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:321","name":"Ellipse 26","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:274","name":"Ellipse 26","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":35,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":35,"y":-195,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":35,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":35,"y":-195,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:320","name":"Ellipse 36","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:284","name":"Ellipse 36","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":35,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":35,"y":-104,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":35,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":35,"y":-104,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:319","name":"Ellipse 46","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:294","name":"Ellipse 46","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":35,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":35,"y":-13,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":35,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":35,"y":-13,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:318","name":"Ellipse 7","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:255","name":"Ellipse 7","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":134,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":134,"y":-377,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":134,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":134,"y":-377,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:317","name":"Ellipse 17","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:265","name":"Ellipse 17","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":134,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":134,"y":-286,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":134,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":134,"y":-286,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:316","name":"Ellipse 27","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:275","name":"Ellipse 27","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":134,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":134,"y":-195,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":134,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":134,"y":-195,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:315","name":"Ellipse 37","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:285","name":"Ellipse 37","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":134,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":134,"y":-104,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":134,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":134,"y":-104,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:314","name":"Ellipse 47","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:295","name":"Ellipse 47","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":134,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":134,"y":-13,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":134,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":134,"y":-13,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:313","name":"Ellipse 8","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:256","name":"Ellipse 8","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":233,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":233,"y":-377,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":233,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":233,"y":-377,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:312","name":"Ellipse 18","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:266","name":"Ellipse 18","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":233,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":233,"y":-286,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":233,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":233,"y":-286,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:311","name":"Ellipse 28","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:276","name":"Ellipse 28","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":233,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":233,"y":-195,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":233,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":233,"y":-195,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:310","name":"Ellipse 38","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:286","name":"Ellipse 38","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":233,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":233,"y":-104,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":233,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":233,"y":-104,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:309","name":"Ellipse 48","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:296","name":"Ellipse 48","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":233,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":233,"y":-13,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":233,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":233,"y":-13,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:308","name":"Ellipse 9","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:257","name":"Ellipse 9","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":332,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":332,"y":-377,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":332,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":332,"y":-377,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:307","name":"Ellipse 19","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:267","name":"Ellipse 19","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":332,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":332,"y":-286,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":332,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":332,"y":-286,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:306","name":"Ellipse 29","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:277","name":"Ellipse 29","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":332,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":332,"y":-195,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":332,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":332,"y":-195,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:305","name":"Ellipse 39","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:287","name":"Ellipse 39","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":332,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":332,"y":-104,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":332,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":332,"y":-104,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:304","name":"Ellipse 49","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:297","name":"Ellipse 49","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":332,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":332,"y":-13,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":332,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":332,"y":-13,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:303","name":"Ellipse 10","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:258","name":"Ellipse 10","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":431,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":431,"y":-377,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":431,"y":-377,"width":88,"height":82},"absoluteRenderBounds":{"x":431,"y":-377,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:302","name":"Ellipse 20","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:268","name":"Ellipse 20","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":431,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":431,"y":-286,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":431,"y":-286,"width":88,"height":82},"absoluteRenderBounds":{"x":431,"y":-286,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:301","name":"Ellipse 30","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:278","name":"Ellipse 30","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":431,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":431,"y":-195,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":431,"y":-195,"width":88,"height":82},"absoluteRenderBounds":{"x":431,"y":-195,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:300","name":"Ellipse 40","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:288","name":"Ellipse 40","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":431,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":431,"y":-104,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":431,"y":-104,"width":88,"height":82},"absoluteRenderBounds":{"x":431,"y":-104,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]},{"id":"1:299","name":"Ellipse 50","type":"COMPONENT","scrollBehavior":"SCROLLS","children":[{"id":"1:298","name":"Ellipse 50","type":"ELLIPSE","scrollBehavior":"SCROLLS","blendMode":"PASS_THROUGH","fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.9859537482261658,"g":0.3247677683830261,"b":0.3247677683830261,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","absoluteBoundingBox":{"x":431,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":431,"y":-13,"width":88,"height":82},"constraints":{"vertical":"SCALE","horizontal":"SCALE"},"effects":[],"arcData":{"startingAngle":0,"endingAngle":6.2831854820251465,"innerRadius":0},"interactions":[]}],"blendMode":"PASS_THROUGH","clipsContent":false,"background":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"fills":[{"blendMode":"NORMAL","visible":false,"type":"SOLID","color":{"r":1,"g":1,"b":1,"a":1}}],"strokes":[],"strokeWeight":1,"strokeAlign":"INSIDE","backgroundColor":{"r":0,"g":0,"b":0,"a":0},"absoluteBoundingBox":{"x":431,"y":-13,"width":88,"height":82},"absoluteRenderBounds":{"x":431,"y":-13,"width":88,"height":82},"constraints":{"vertical":"TOP","horizontal":"LEFT"},"effects":[],"interactions":[]}],"backgroundColor":{"r":0.9607843160629272,"g":0.9607843160629272,"b":0.9607843160629272,"a":1},"prototypeStartNodeID":null,"flowStartingPoints":[],"prototypeDevice":{"type":"NONE","rotation":"NONE"}}]},"components":{"1:3":{"key":"3f9489391619753868b8abc26c94d77a48669c26","name":"PrimaryButton","description":"","remote":false,"documentationLinks":[]},"1:203":{"key":"85ddd83b0860d4aea56af03e788d63702ba552c4","name":"Component 21","description":"","remote":false,"documentationLinks":[]},"1:201":{"key":"403e72a15cb4eabbe96cc4e1502cec0ed2fcf712","name":"Component 11","description":"","remote":false,"documentationLinks":[]},"1:205":{"key":"2d06cc4c1c264e5984ef635d2f0fc2e57bb49f2d","name":"Component 22","description":"","remote":false,"documentationLinks":[]},"1:207":{"key":"34763e3bd9fd277674e3e32e2ddd703311c9e50a","name":"Component 3","description":"","remote":false,"documentationLinks":[]},"1:229":{"key":"220f143d34a5b165ece760fd8fcb34e02295897e","name":"Component 23","description":"","remote":false,"documentationLinks":[]},"1:217":{"key":"2ae7c077475ac163e3e001d2ac38c01c53e1d0b7","name":"Component 12","description":"","remote":false,"documentationLinks":[]},"1:245":{"key":"cc9618e85fa42317ccd8464109425ca9482cc699","name":"Component 24","description":"","remote":false,"documentationLinks":[]},"1:211":{"key":"44479b4ebd27f7070682d0836075494dd16efbce","name":"Component 5","description":"","remote":false,"documentationLinks":[]},"1:210":{"key":"79446f41d2a5b399538ce0f0a01e8f51b8c205cd","name":"Component 41","description":"","remote":false,"documentationLinks":[]},"1:228":{"key":"0c8ed512432114652294f3ffb69e33d0dd528ac5","name":"Component 25","description":"","remote":false,"documentationLinks":[]},"1:227":{"key":"81ff099dbae0943476e0bd7e3f2d872961661019","name":"Component 42","description":"","remote":false,"documentationLinks":[]},"1:220":{"key":"90531ee21f795cf56eab546c4689f52c4062a78d","name":"Component 13","description":"","remote":false,"documentationLinks":[]},"1:219":{"key":"c6c3ca7881d0bb7f2230bc44c10a64c25a6ef01b","name":"Component 43","description":"","remote":false,"documentationLinks":[]},"1:244":{"key":"25f64087511742bc405a8fa9bf1640e94ce6a690","name":"Component 26","description":"","remote":false,"documentationLinks":[]},"1:243":{"key":"86e64c2a7c4938797f99ccdcb257fd0287d8dead","name":"Component 44","description":"","remote":false,"documentationLinks":[]},"1:209":{"key":"872ef4662118630e24fa36986a8fad209d4f4eb8","name":"Component 7","description":"","remote":false,"documentationLinks":[]},"1:246":{"key":"8385d3ba15a78e1a3c4c2a0d04ade796509ce061","name":"Component 27","description":"","remote":false,"documentationLinks":[]},"1:221":{"key":"2d9b03219fb759cc916de76de8b97847c8d1afe4","name":"Component 14","description":"","remote":false,"documentationLinks":[]},"1:242":{"key":"e0ef779e22197edaabc2d65c5aa9eaac5a4dd453","name":"Component 28","description":"","remote":false,"documentationLinks":[]},"1:208":{"key":"8ef72ce946c8a406e5e45f0fbc458b66bff09d2c","name":"Component 9","description":"","remote":false,"documentationLinks":[]},"1:241":{"key":"3784416232845b51a4baf673194e360777931be8","name":"Component 29","description":"","remote":false,"documentationLinks":[]},"1:218":{"key":"b164e4752850dfe2d2c6000ac49cbb5b7afb00e8","name":"Component 15","description":"","remote":false,"documentationLinks":[]},"1:240":{"key":"95912d0ca9037811dc9244c6e943dc5779494019","name":"Component 30","description":"","remote":false,"documentationLinks":[]},"1:10":{"key":"368c55599a24f4b875a22dae3b0e3eddcea9ee2a","name":"Component 2","description":"","remote":false,"documentationLinks":[]},"1:204":{"key":"8aa201988a3634b1e66560bef2e8a031dcdba7f1","name":"Component 31","description":"","remote":false,"documentationLinks":[]},"1:202":{"key":"ac7d094d5fbf5a722a9922941e9202a705308cc0","name":"Component 16","description":"","remote":false,"documentationLinks":[]},"1:206":{"key":"e5c43706c6b4264cd2f9db5f54e6aa9319bd487e","name":"Component 32","description":"","remote":false,"documentationLinks":[]},"1:216":{"key":"2125542f0fbf5bf745af460c3e4e673b0cb30fd0","name":"Component 4","description":"","remote":false,"documentationLinks":[]},"1:239":{"key":"ecb402d86888bf0cc5b97dfdf7b99d29bb261128","name":"Component 33","description":"","remote":false,"documentationLinks":[]},"1:222":{"key":"1327aefd2361783fcf84378a1a5bd71aa8f06320","name":"Component 17","description":"","remote":false,"documentationLinks":[]},"1:238":{"key":"fde379ac5c7092e730bb40c62715924829acac88","name":"Component 34","description":"","remote":false,"documentationLinks":[]},"1:213":{"key":"02a872399d9b6604b84ab54550860268f50c9f57","name":"Component 6","description":"","remote":false,"documentationLinks":[]},"1:212":{"key":"8b51fc1edc8bf19f114fbb77a057321631508e2f","name":"Component 45","description":"","remote":false,"documentationLinks":[]},"1:237":{"key":"a677308ad98b55123a7b1246c46ead6e2f71e108","name":"Component 35","description":"","remote":false,"documentationLinks":[]},"1:236":{"key":"f154388479f391ff86910e8fd7dbb39f86592b8b","name":"Component 46","description":"","remote":false,"documentationLinks":[]},"1:225":{"key":"e2fbf2385f8fefde664dfb205d33ee9f15639a3c","name":"Component 18","description":"","remote":false,"documentationLinks":[]},"1:226":{"key":"90215a41c7dd2ef6c1ba9a945d4660053826afc9","name":"Component 47","description":"","remote":false,"documentationLinks":[]},"1:235":{"key":"92aa607157745d90a710da90e206bb099dafd39d","name":"Component 36","description":"","remote":false,"documentationLinks":[]},"1:234":{"key":"7d5da8c6f6183c0b791c59521f118f09d5068dfd","name":"Component 48","description":"","remote":false,"documentationLinks":[]},"1:214":{"key":"922e8ecfd54aef856d8bb9f5d03caba5d4fabe3d","name":"Component 8","description":"","remote":false,"documentationLinks":[]},"1:233":{"key":"3a0bbf79e5df7e876d11e840688bfcea8d61dea0","name":"Component 37","description":"","remote":false,"documentationLinks":[]},"1:224":{"key":"688fa5e7cdd6f015ffc8ae70b4a349131f514bf3","name":"Component 19","description":"","remote":false,"documentationLinks":[]},"1:232":{"key":"3788b98cf8d265b0fedf72adb7f469d3079d527c","name":"Component 38","description":"","remote":false,"documentationLinks":[]},"1:215":{"key":"bd66401ca718d2362f9cd75965569b057cc002b2","name":"Component 10","description":"","remote":false,"documentationLinks":[]},"1:231":{"key":"97baef1d2287a2b34216761558d762398b26f053","name":"Component 39","description":"","remote":false,"documentationLinks":[]},"1:223":{"key":"bf29b2c5df27400419fe597b052e722695c74a4c","name":"Component 20","description":"","remote":false,"documentationLinks":[]},"1:230":{"key":"0e853fe7ff4541a60debd6326b5a0f863ef04fdd","name":"Component 40","description":"","remote":false,"documentationLinks":[]},"1:348":{"key":"cd1819073ab50cf41a5e908e13903abb0525d992","name":"Ellipse 1","description":"","remote":false,"documentationLinks":[]},"1:347":{"key":"6146f9d1f85023b9beb88f2fbcd98918ae007635","name":"Ellipse 11","description":"","remote":false,"documentationLinks":[]},"1:346":{"key":"7c954cb8007f209e2b5c72852eb4336b23da1734","name":"Ellipse 21","description":"","remote":false,"documentationLinks":[]},"1:345":{"key":"25488e4643809501cdeb92c97d9b3f7ef0d74e7a","name":"Ellipse 31","description":"","remote":false,"documentationLinks":[]},"1:344":{"key":"9e2b25d1c848d550ebd6fc007353cd272c1bb798","name":"Ellipse 41","description":"","remote":false,"documentationLinks":[]},"1:343":{"key":"53d3f895c906d7b84120bf7f0458175278a1b0ab","name":"Ellipse 2","description":"","remote":false,"documentationLinks":[]},"1:342":{"key":"87fd3b7b45e6fe8656d6225909f2988db3db5580","name":"Ellipse 12","description":"","remote":false,"documentationLinks":[]},"1:341":{"key":"767e9b3ac6d8bbca87385299d32c6cfdcf6831c8","name":"Ellipse 22","description":"","remote":false,"documentationLinks":[]},"1:340":{"key":"0bab1ff8fe36ef6090aa635f32e79ff379cd7c4e","name":"Ellipse 32","description":"","remote":false,"documentationLinks":[]},"1:339":{"key":"6b6ef9a233d1c7d3499db3dab95aa74c288b714d","name":"Ellipse 42","description":"","remote":false,"documentationLinks":[]},"1:338":{"key":"479677520cb6d6f33cd27e95546812fc91ff58de","name":"Ellipse 3","description":"","remote":false,"documentationLinks":[]},"1:337":{"key":"733d279c8b673959cf2616609dd93b99b6369501","name":"Ellipse 13","description":"","remote":false,"documentationLinks":[]},"1:336":{"key":"4c1675f44ea7aac6a53449064a29bb5e9bb881fc","name":"Ellipse 23","description":"","remote":false,"documentationLinks":[]},"1:335":{"key":"7d439b521b0fb455431b2f47d747a948c6616795","name":"Ellipse 33","description":"","remote":false,"documentationLinks":[]},"1:334":{"key":"e9cb90ef7f28ce38fb00c945cf73d988f11167ad","name":"Ellipse 43","description":"","remote":false,"documentationLinks":[]},"1:333":{"key":"2a25ad949c79f81395765239982328687c0dbb20","name":"Ellipse 4","description":"","remote":false,"documentationLinks":[]},"1:332":{"key":"0436a6aa58a9581c8b85f20bcbadb55ed70f16db","name":"Ellipse 14","description":"","remote":false,"documentationLinks":[]},"1:331":{"key":"88f659cf523e22cbc09f1f6b8abe31d20f71b330","name":"Ellipse 24","description":"","remote":false,"documentationLinks":[]},"1:330":{"key":"2f7b7063dd0e9d68eefc1c3d97d8b9034d7cae75","name":"Ellipse 34","description":"","remote":false,"documentationLinks":[]},"1:329":{"key":"a9a2ca976aff3bd7dc6535f5cce8b1733c67b886","name":"Ellipse 44","description":"","remote":false,"documentationLinks":[]},"1:328":{"key":"04d8bf079b14bcc5341b90dc7e31dc38bb8ac903","name":"Ellipse 5","description":"","remote":false,"documentationLinks":[]},"1:327":{"key":"e66ac1911969301db187676a38457c4121a9f3af","name":"Ellipse 15","description":"","remote":false,"documentationLinks":[]},"1:326":{"key":"e8b4b36e8141739524472653fe5f625565384aac","name":"Ellipse 25","description":"","remote":false,"documentationLinks":[]},"1:325":{"key":"5c735f977858674525e1e8210a53ac16e02da438","name":"Ellipse 35","description":"","remote":false,"documentationLinks":[]},"1:324":{"key":"7a2db429f64840b6a6a5991d545970b8001a5652","name":"Ellipse 45","description":"","remote":false,"documentationLinks":[]},"1:323":{"key":"765c002b88fe5d3014ccd8788c6da81d287616e5","name":"Ellipse 6","description":"","remote":false,"documentationLinks":[]},"1:322":{"key":"49bc8b1a6dcb9cbe2192b118a15514142f35946f","name":"Ellipse 16","description":"","remote":false,"documentationLinks":[]},"1:321":{"key":"9a8c3a67c06a16aa662a745bf66fd9b7f1f7c545","name":"Ellipse 26","description":"","remote":false,"documentationLinks":[]},"1:320":{"key":"0919ec587ec7a3193e11a45f5a738d1aa17a5e12","name":"Ellipse 36","description":"","remote":false,"documentationLinks":[]},"1:319":{"key":"60886135b3b80f37801adb65eb5cd5b081e0bcc2","name":"Ellipse 46","description":"","remote":false,"documentationLinks":[]},"1:318":{"key":"c1ded1376fa149a0e52f3657b995471df3728e15","name":"Ellipse 7","description":"","remote":false,"documentationLinks":[]},"1:317":{"key":"071cda83a5573386c319423a3b82dd3751d29521","name":"Ellipse 17","description":"","remote":false,"documentationLinks":[]},"1:316":{"key":"3d3a5a132e1b243ab57e0e4b3e2249e2c4fadad7","name":"Ellipse 27","description":"","remote":false,"documentationLinks":[]},"1:315":{"key":"f0e993d58d8784c0dc14fc8b760905534e1d01ac","name":"Ellipse 37","description":"","remote":false,"documentationLinks":[]},"1:314":{"key":"851ca8cab264564c86e19fe52bf680743ba9f282","name":"Ellipse 47","description":"","remote":false,"documentationLinks":[]},"1:313":{"key":"3bf739b4748874085bf957068ee3bb1202eafa78","name":"Ellipse 8","description":"","remote":false,"documentationLinks":[]},"1:312":{"key":"afd81e380099d67fdf211fb8e3986c1c0bc9156d","name":"Ellipse 18","description":"","remote":false,"documentationLinks":[]},"1:311":{"key":"2bc30ecfc9da091f4065a39e52e39391eeb9f4c3","name":"Ellipse 28","description":"","remote":false,"documentationLinks":[]},"1:310":{"key":"ea91663dccb1931431c748d208f06d0c36d1153b","name":"Ellipse 38","description":"","remote":false,"documentationLinks":[]},"1:309":{"key":"22484fcb8622f0379a362736425fa1aa52330c2d","name":"Ellipse 48","description":"","remote":false,"documentationLinks":[]},"1:308":{"key":"0dde7083d1f39d412decc3208929cc4549c115b8","name":"Ellipse 9","description":"","remote":false,"documentationLinks":[]},"1:307":{"key":"d404ecd5439edc18becdd9d978d225f803ae62ca","name":"Ellipse 19","description":"","remote":false,"documentationLinks":[]},"1:306":{"key":"df2fe42d144565f8bc5442dce6a02d6621db71cd","name":"Ellipse 29","description":"","remote":false,"documentationLinks":[]},"1:305":{"key":"18e4fc1b227aaa2ea9dc9611c23f50ae1b1fe086","name":"Ellipse 39","description":"","remote":false,"documentationLinks":[]},"1:304":{"key":"99c3c1a600776da3836d433a3b9abb5c6b05b2c0","name":"Ellipse 49","description":"","remote":false,"documentationLinks":[]},"1:303":{"key":"8d2be91fbc41428b0c038810287d03dc0e7d87d6","name":"Ellipse 10","description":"","remote":false,"documentationLinks":[]},"1:302":{"key":"1aae99d31f9ba6efa38c1a521ca77f5a97c77507","name":"Ellipse 20","description":"","remote":false,"documentationLinks":[]},"1:301":{"key":"4fc491567ce15800f95b5595b1ccbbec48b3c792","name":"Ellipse 30","description":"","remote":false,"documentationLinks":[]},"1:300":{"key":"215665aea49f2e7260677cf7f79a5041c456f0d7","name":"Ellipse 40","description":"","remote":false,"documentationLinks":[]},"1:299":{"key":"c46d63de34c8b9cf17ce1a66fb899ddc23119c55","name":"Ellipse 50","description":"","remote":false,"documentationLinks":[]}},"componentSets":{},"schemaVersion":0,"styles":{},"name":"Many Dummy Components","lastModified":"2025-01-07T15:33:39Z","thumbnailUrl":"https://s3-staging.staging.figma.com/thumbnails/f4039af2-6ba0-4f05-a716-eee1f733c987?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA56VQ2PUNPFD2UQOS%2F20250105%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250105T120000Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=c3e2c47f2dc7942ed131b35ba2a18552bbc9fd7019b4aefd2b1d2cdd3d6a1f63","version":"2170832056625071133","role":"owner","editorType":"figma","linkAccess":"org_view"} diff --git a/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactApiComponent.figmadoc.tsx b/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactApiComponent.figmadoc.tsx deleted file mode 100644 index 164fb08..0000000 --- a/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactApiComponent.figmadoc.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import figma from '../../../../../react/index_react' - -import { ReactApiComponent } from './ReactApiComponent' - -figma.connect(ReactApiComponent, 'ui/button') diff --git a/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactButtonComponent.figmadoc.tsx b/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactButtonComponent.figmadoc.tsx new file mode 100644 index 0000000..ec68894 --- /dev/null +++ b/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactButtonComponent.figmadoc.tsx @@ -0,0 +1,5 @@ +import figma from '../../../../../react/index_react' + +import { ReactButtonComponent } from './ReactButtonComponent' + +figma.connect(ReactButtonComponent, 'ui/button') diff --git a/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactApiComponent.tsx b/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactButtonComponent.tsx similarity index 76% rename from cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactApiComponent.tsx rename to cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactButtonComponent.tsx index 2397c9e..db35785 100644 --- a/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactApiComponent.tsx +++ b/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactButtonComponent.tsx @@ -11,6 +11,6 @@ interface ButtonProps { * @param disabled disable the button * @returns JSX element */ -export const ReactApiComponent = ({ children, disabled = false }: ButtonProps) => { +export const ReactButtonComponent = ({ children, disabled = false }: ButtonProps) => { return } diff --git a/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactLabelComponent.figmadoc.jsx b/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactLabelComponent.figmadoc.jsx new file mode 100644 index 0000000..bad393b --- /dev/null +++ b/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactLabelComponent.figmadoc.jsx @@ -0,0 +1,4 @@ +import figma from '../../../../../react/index_react' +import { ReactLabelComponent } from './ReactLabelComponent' + +figma.connect(ReactLabelComponent, 'ui/label') diff --git a/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactLabelComponent.tsx b/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactLabelComponent.tsx new file mode 100644 index 0000000..1c53967 --- /dev/null +++ b/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactLabelComponent.tsx @@ -0,0 +1,16 @@ +import React from 'react' + +interface LabelProps { + disabled: boolean + children: any +} + +/** + * @description This is a label + * @param children text to render + * @param disabled disable the button + * @returns JSX element + */ +export const ReactLabelComponent = ({ children, disabled = false }: LabelProps) => { + return
{children}
+} diff --git a/cli/src/connect/__test__/e2e/e2e_parse_command/swift_package/Package.resolved b/cli/src/connect/__test__/e2e/e2e_parse_command/swift_package/Package.resolved index 16cf882..879983f 100644 --- a/cli/src/connect/__test__/e2e/e2e_parse_command/swift_package/Package.resolved +++ b/cli/src/connect/__test__/e2e/e2e_parse_command/swift_package/Package.resolved @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/nicklockwood/SwiftFormat", "state" : { - "revision" : "dd989a46d0c6f15c016484bab8afe5e7a67a4022", - "version" : "0.54.0" + "revision" : "2d5a2b6bde636c1feae2c852ab9a50f221e98c66", + "version" : "0.55.3" } } ], diff --git a/cli/src/connect/__test__/e2e/e2e_parse_command/swift_parser/swift_parser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/cli/src/connect/__test__/e2e/e2e_parse_command/swift_parser/swift_parser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 26f194d..962fe60 100644 --- a/cli/src/connect/__test__/e2e/e2e_parse_command/swift_parser/swift_parser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/cli/src/connect/__test__/e2e/e2e_parse_command/swift_parser/swift_parser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/nicklockwood/SwiftFormat", "state" : { - "revision" : "05cb325003a673b3d177c711b3bc909cfee07622", - "version" : "0.53.9" + "revision" : "2d5a2b6bde636c1feae2c852ab9a50f221e98c66", + "version" : "0.55.3" } } ], diff --git a/cli/src/connect/__test__/e2e/e2e_parse_command/swift_wizard/swift_wizard.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/cli/src/connect/__test__/e2e/e2e_parse_command/swift_wizard/swift_wizard.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 26f194d..962fe60 100644 --- a/cli/src/connect/__test__/e2e/e2e_parse_command/swift_wizard/swift_wizard.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/cli/src/connect/__test__/e2e/e2e_parse_command/swift_wizard/swift_wizard.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/nicklockwood/SwiftFormat", "state" : { - "revision" : "05cb325003a673b3d177c711b3bc909cfee07622", - "version" : "0.53.9" + "revision" : "2d5a2b6bde636c1feae2c852ab9a50f221e98c66", + "version" : "0.55.3" } } ], diff --git a/cli/src/connect/__test__/e2e/e2e_parse_command_custom.test.ts b/cli/src/connect/__test__/e2e/e2e_parse_command_custom.test.ts index c8cfae8..1c0eba9 100644 --- a/cli/src/connect/__test__/e2e/e2e_parse_command_custom.test.ts +++ b/cli/src/connect/__test__/e2e/e2e_parse_command_custom.test.ts @@ -8,22 +8,28 @@ describe('e2e test for `parse` command custom parsers', () => { const testPath = path.join(__dirname, `e2e_parse_command/${testName}`) const json = JSON.parse(result.stdout) - expect(json).toMatchObject([ - { - figmaNode: `${path.join(testPath, 'Test.test')}`, - template: - '{"config":{"parser":"custom","parserCommand":"node parser/custom_parser.js","include":["*.test"],"exclude":["Excluded.test"]},"mode":"PARSE"}', - label: 'Test', - source: `https://github.com/figma/code-connect/blob/main/cli/src/connect/__test__/e2e/e2e_parse_command/${testName}/Test.test`, - }, - { - figmaNode: `${path.join(testPath, 'OtherFile.test')}`, - template: - '{"config":{"parser":"custom","parserCommand":"node parser/custom_parser.js","include":["*.test"],"exclude":["Excluded.test"]},"mode":"PARSE"}', - label: 'Test', - source: `https://github.com/figma/code-connect/blob/main/cli/src/connect/__test__/e2e/e2e_parse_command/${testName}/OtherFile.test`, - }, - ]) + expect(json).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + figmaNode: `${path.join(testPath, 'Test.test')}`, + template: + '{"config":{"parser":"custom","parserCommand":"node parser/custom_parser.js","include":["*.test"],"exclude":["Excluded.test"]},"mode":"PARSE"}', + label: 'Test', + source: expect.stringMatching( + /https:\/\/github\.com\/figma\/[a-z-/]+\/cli\/src\/connect\/__test__\/e2e\/e2e_parse_command\/\w+\/Test\.test/, + ), + }), + expect.objectContaining({ + figmaNode: `${path.join(testPath, 'OtherFile.test')}`, + template: + '{"config":{"parser":"custom","parserCommand":"node parser/custom_parser.js","include":["*.test"],"exclude":["Excluded.test"]},"mode":"PARSE"}', + label: 'Test', + source: expect.stringMatching( + /https:\/\/github\.com\/figma\/[a-z-/]+\/cli\/src\/connect\/__test__\/e2e\/e2e_parse_command\/\w+\/OtherFile\.test/, + ), + }), + ]), + ) } it('successfully calls a custom parser executable', async () => { diff --git a/cli/src/connect/__test__/e2e/e2e_parse_command_parser_executables.test.ts b/cli/src/connect/__test__/e2e/e2e_parse_command_parser_executables.test.ts index 674ce68..d68af9f 100644 --- a/cli/src/connect/__test__/e2e/e2e_parse_command_parser_executables.test.ts +++ b/cli/src/connect/__test__/e2e/e2e_parse_command_parser_executables.test.ts @@ -8,22 +8,28 @@ describe('e2e test for `parse` command (parser executables)', () => { const testPath = path.join(__dirname, `e2e_parse_command/${testName}`) const json = JSON.parse(result.stdout) - expect(json).toMatchObject([ - { - figmaNode: `${path.join(testPath, 'Test.test')}`, - template: - '{"config":{"parser":"__unit_test__","include":["*.test"],"exclude":["Excluded.test"]},"mode":"PARSE"}', - label: 'Test', - source: `https://github.com/figma/code-connect/blob/main/cli/src/connect/__test__/e2e/e2e_parse_command/${testName}/Test.test`, - }, - { - figmaNode: `${path.join(testPath, 'OtherFile.test')}`, - template: - '{"config":{"parser":"__unit_test__","include":["*.test"],"exclude":["Excluded.test"]},"mode":"PARSE"}', - label: 'Test', - source: `https://github.com/figma/code-connect/blob/main/cli/src/connect/__test__/e2e/e2e_parse_command/${testName}/OtherFile.test`, - }, - ]) + expect(json).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + figmaNode: `${path.join(testPath, 'Test.test')}`, + template: + '{"config":{"parser":"__unit_test__","include":["*.test"],"exclude":["Excluded.test"]},"mode":"PARSE"}', + label: 'Test', + source: expect.stringMatching( + /https:\/\/github\.com\/figma\/[a-z-/]+\/cli\/src\/connect\/__test__\/e2e\/e2e_parse_command\/\w+\/Test\.test/, + ), + }), + expect.objectContaining({ + figmaNode: `${path.join(testPath, 'OtherFile.test')}`, + template: + '{"config":{"parser":"__unit_test__","include":["*.test"],"exclude":["Excluded.test"]},"mode":"PARSE"}', + label: 'Test', + source: expect.stringMatching( + /https:\/\/github\.com\/figma\/[a-z-/]+\/cli\/src\/connect\/__test__\/e2e\/e2e_parse_command\/\w+\/OtherFile\.test/, + ), + }), + ]), + ) } it('successfully calls a first party parser executable', async () => { diff --git a/cli/src/connect/__test__/e2e/e2e_parse_command_react.test.ts b/cli/src/connect/__test__/e2e/e2e_parse_command_react.test.ts index f7857af..9083875 100644 --- a/cli/src/connect/__test__/e2e/e2e_parse_command_react.test.ts +++ b/cli/src/connect/__test__/e2e/e2e_parse_command_react.test.ts @@ -6,6 +6,10 @@ import path from 'path' describe('e2e test for `parse` command (React)', () => { const cliVersion = require('../../../../package.json').version + /** + * @description Test that the parse command successfully parses a React file using + * both a JSX and TSX Figmadoc file, and Storybook file. + */ it('successfully parses both React and Storybook files', async () => { const testPath = path.join(__dirname, 'e2e_parse_command/react_storybook') @@ -16,51 +20,77 @@ describe('e2e test for `parse` command (React)', () => { }, ) + // Check that both JSX and TSX Figmadoc files are parsed + const components = ['ReactLabelComponent.figmadoc.jsx', 'ReactButtonComponent.figmadoc.tsx'] + const componentPaths = components.map((c) => path.join(testPath, c)).join('\n') + expect(tidyStdOutput(result.stderr)).toBe( `No config file found in ${testPath}, proceeding with default options Using "react" parser as package.json containing a "react" dependency was found in ${testPath}. If this is incorrect, please check you are running Code Connect from your project root, or add a \`parser\` key to your config file. See https://github.com/figma/code-connect for more information. -${path.join(testPath, 'ReactApiComponent.figmadoc.tsx')}`, +${componentPaths}`, ) const json = JSON.parse(result.stdout) - expect(json).toMatchObject([ - { - figmaNode: 'ui/button', - label: 'React', - language: 'typescript', - component: 'ReactApiComponent', - source: - 'https://github.com/figma/code-connect/blob/main/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/ReactApiComponent.tsx', - sourceLocation: { line: 13 }, - template: `const figma = require(\"figma\") + expect(json).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + figmaNode: 'ui/label', + label: 'React', + language: 'typescript', + component: 'ReactLabelComponent', + source: expect.stringMatching( + /https:\/\/github\.com\/figma\/[a-z-/]+\/cli\/src\/connect\/__test__\/e2e\/e2e_parse_command\/react_storybook\/ReactLabelComponent\.tsx/, + ), + sourceLocation: { line: 13 }, + template: `const figma = require(\"figma\") -export default figma.tsx\`\``, - templateData: { - imports: ["import { ReactApiComponent } from './ReactApiComponent'"], - }, - metadata: { - cliVersion, - }, - }, - { - figmaNode: 'https://figma.com/test', - source: - 'https://github.com/figma/code-connect/blob/main/cli/src/connect/__test__/e2e/e2e_parse_command/react_storybook/StorybookComponent.tsx', - sourceLocation: { line: 8 }, - templateData: { imports: [] }, - component: 'StorybookComponent', - label: 'Storybook', - language: 'typescript', - metadata: { - cliVersion, - }, - }, - ]) +export default figma.tsx\`\``, + templateData: { + imports: ["import { ReactLabelComponent } from './ReactLabelComponent'"], + nestable: true, + }, + metadata: { + cliVersion, + }, + }), + expect.objectContaining({ + figmaNode: 'ui/button', + label: 'React', + language: 'typescript', + component: 'ReactButtonComponent', + source: expect.stringMatching( + /https:\/\/github\.com\/figma\/[a-z-/]+\/cli\/src\/connect\/__test__\/e2e\/e2e_parse_command\/react_storybook\/ReactButtonComponent\.tsx/, + ), + sourceLocation: { line: 13 }, + template: `const figma = require(\"figma\") + +export default figma.tsx\`\``, + templateData: { + imports: ["import { ReactButtonComponent } from './ReactButtonComponent'"], + nestable: true, + }, + metadata: { + cliVersion, + }, + }), + expect.objectContaining({ + figmaNode: 'https://figma.com/test', + source: expect.stringMatching( + /https:\/\/github.com\/figma\/[a-z-/]+\/cli\/src\/connect\/__test__\/e2e\/e2e_parse_command\/react_storybook\/StorybookComponent.tsx/, + ), + sourceLocation: { line: 8 }, + templateData: { imports: [] }, + component: 'StorybookComponent', + label: 'Storybook', + language: 'typescript', + }), + ]), + ) - expect(json[1].template.startsWith('function _fcc_renderReactProp')).toBe(true) + expect(json[2].template.startsWith('function _fcc_renderReactProp')).toBe(true) // We don't care about checking the contents of the function as this can change expect( - json[1].template.endsWith( + json[2].template.endsWith( "const figma = require('figma')\n\nexport default figma.tsx`Hello`\n", ), ).toBe(true) diff --git a/cli/src/connect/__test__/e2e/e2e_wizard_many_components.test.ts b/cli/src/connect/__test__/e2e/e2e_wizard_many_components.test.ts new file mode 100644 index 0000000..bdfb022 --- /dev/null +++ b/cli/src/connect/__test__/e2e/e2e_wizard_many_components.test.ts @@ -0,0 +1,52 @@ +import { promisify } from 'util' +import { exec } from 'child_process' +import { tidyStdOutput } from '../../../__test__/utils' +import path from 'path' +import fs from 'fs' + +const TEST_PATH = path.join(__dirname, 'e2e_parse_command/react_wizard') + +const OUTPUT_FOLDER = `e2e-wizard-many-components-folder` +const OUTPUT_PATH = path.join(__dirname, OUTPUT_FOLDER) + +describe('e2e wizard with many components', () => { + beforeEach(() => { + fs.rmSync(OUTPUT_PATH, { recursive: true, force: true }) + }) + afterEach(() => { + fs.rmSync(OUTPUT_PATH, { recursive: true, force: true }) + }) + it( + 'it reaches the point of asking to select pages', + async () => { + const mockDocPath = path.join( + path.join(TEST_PATH, '..'), + 'dummy_api_response_for_wizard_many_components.json', + ) + + const wizardAnswers = JSON.stringify([ + 'figd_123', // Access token, + 'no', // don't create an .env file to store the access token + `${TEST_PATH}/components`, // Top-level components directory + 'https://www.figma.com/design/abc123/my-design-system', // Design system URL + 'no', // don't create a config file + 'no', // don't use ai + ['0:1'], // select the first page + '', // Don't select any links to edit + OUTPUT_FOLDER, // output in the folder + ]).replace(/"/g, '\\"') + + const result = await promisify(exec)( + `npx cross-env CODE_CONNECT_MOCK_DOC_RESPONSE=${mockDocPath} WIZARD_ANSWERS_TO_PREFILL="${wizardAnswers}" npx tsx ../../../cli connect --skip-update-check --dir ${TEST_PATH}`, + { + cwd: __dirname, + }, + ) + + expect(tidyStdOutput(result.stderr)).toContain( + '98 Figma components found in the Figma file across 2 pages', + ) + }, + 5 * 60 * 1000, + ) +}) diff --git a/cli/src/connect/__test__/e2e/e2e_wizard_react.test.ts b/cli/src/connect/__test__/e2e/e2e_wizard_react.test.ts index d65add4..63386cf 100644 --- a/cli/src/connect/__test__/e2e/e2e_wizard_react.test.ts +++ b/cli/src/connect/__test__/e2e/e2e_wizard_react.test.ts @@ -6,4 +6,5 @@ testWizardE2e({ componentsPath: './e2e_parse_command/react_wizard/components', expectedCreatedComponentPath: 'components/PrimaryButton.figma.tsx', expectedIncludeGlobs: ['components/**/*.{tsx,jsx}'], + expectedLabel: 'React', }) diff --git a/cli/src/connect/__test__/e2e/e2e_wizard_swift.test.ts b/cli/src/connect/__test__/e2e/e2e_wizard_swift.test.ts index 907a1c9..57eb8b1 100644 --- a/cli/src/connect/__test__/e2e/e2e_wizard_swift.test.ts +++ b/cli/src/connect/__test__/e2e/e2e_wizard_swift.test.ts @@ -9,4 +9,5 @@ testWizardE2e({ componentsPath: './e2e_parse_command/swift_wizard/swift_wizard', expectedCreatedComponentPath: 'swift_wizard/Primary Button.figma.swift', expectedIncludeGlobs: ['swift_wizard/**/*.swift'], + expectedLabel: undefined, }) diff --git a/cli/src/connect/__test__/e2e/test_wizard_e2e.ts b/cli/src/connect/__test__/e2e/test_wizard_e2e.ts index cd23205..7b5ff0e 100644 --- a/cli/src/connect/__test__/e2e/test_wizard_e2e.ts +++ b/cli/src/connect/__test__/e2e/test_wizard_e2e.ts @@ -11,6 +11,7 @@ export function testWizardE2e(testCase: { componentsPath: string expectedCreatedComponentPath: string expectedIncludeGlobs: string[] + expectedLabel?: string }) { describe(`e2e test for the wizard - ${testCase.name}`, () => { let result: { @@ -38,7 +39,8 @@ export function testWizardE2e(testCase: { } const wizardAnswers = [ - 'figd_123', // Access token + 'figd_123', // Access token, + 'yes', // Create an .env file to store the access token testCase.componentsPath, // Top-level components directory 'https://www.figma.com/design/abc123/my-design-system', // Design system URL 'yes', // Confirm create a new config file @@ -71,17 +73,27 @@ export function testWizardE2e(testCase: { it('creates config file from given answers', () => { const configPath = path.join(testPath, 'figma.config.json') - expect(fs.readFileSync(configPath, 'utf8')).toBe(`\ -{ + + const expectedConfig = testCase.expectedLabel + ? `{ + "codeConnect": { + "include": ${JSON.stringify(testCase.expectedIncludeGlobs)}, + "label": "${testCase.expectedLabel}" + } +} +` + : `{ "codeConnect": { "include": ${JSON.stringify(testCase.expectedIncludeGlobs)} } } -`) +` + + expect(fs.readFileSync(configPath, 'utf8')).toBe(expectedConfig) }) it('reaches linking step', () => { - expect(tidyStdOutput(result.stderr)).toContain('Connecting your components') + expect(tidyStdOutput(result.stderr)).toContain('Connecting your Figma components') }) it('creates Code Connect file at correct location', () => { diff --git a/cli/src/connect/__test__/helpers.test.ts b/cli/src/connect/__test__/helpers.test.ts new file mode 100644 index 0000000..a1fe098 --- /dev/null +++ b/cli/src/connect/__test__/helpers.test.ts @@ -0,0 +1,40 @@ +import path from 'path' +import fs from 'fs' +import { findComponentsInDocument } from '../helpers' + +const ONE_PAGE_RESPONSE = JSON.parse( + fs.readFileSync( + path.join(__dirname, './e2e/e2e_parse_command/dummy_api_response_for_wizard.json'), + 'utf8', + ), +) +const MANY_PAGES_RESPONSE = JSON.parse( + fs.readFileSync( + path.join( + __dirname, + './e2e/e2e_parse_command/dummy_api_response_for_wizard_many_components.json', + ), + 'utf8', + ), +) + +const uniquePageIds = (result: any) => { + return Array.from(new Set(result.map((c: any) => c.pageId))) +} +describe('findComponentsInDocument', () => { + it('all the component have the pageId if there is one page in the document', () => { + const result = findComponentsInDocument(ONE_PAGE_RESPONSE.document) + + expect(result).toHaveLength(7) + + expect(uniquePageIds(result)).toEqual(['0:1']) + }) + + it('all the component have the pageId if there are multiple pages in the document', () => { + const result = findComponentsInDocument(MANY_PAGES_RESPONSE.document) + + expect(result).toHaveLength(98) + + expect(uniquePageIds(result)).toEqual(['0:1', '1:247']) + }) +}) diff --git a/cli/src/connect/__test__/project.test.ts b/cli/src/connect/__test__/project.test.ts index a8d5d63..ab6a582 100644 --- a/cli/src/connect/__test__/project.test.ts +++ b/cli/src/connect/__test__/project.test.ts @@ -86,5 +86,29 @@ describe('Project helper functions', () => { 'https://github.com/myorg/myrepo/blob/master/path/file.ts', ) }) + + it('handles gitlab repo urls', () => { + expect(getRemoteFileUrl('/path/file.ts', 'git@gitlab.com:myorg/myrepo.git')).toBe( + 'https://gitlab.com/myorg/myrepo/-/blob/master/path/file.ts', + ) + }) + + it('handles Bitbucket repo urls', () => { + expect(getRemoteFileUrl('/path/file.ts', 'git@bitbucket.org:myorg/myrepo.git')).toBe( + 'https://bitbucket.org/myorg/myrepo/src/master/path/file.ts', + ) + }) + + it('handles Azure repo urls', () => { + expect( + getRemoteFileUrl('/path/file.ts', 'git@ssh.dev.azure.com:v3/myorg/myrepo/myrepo'), + ).toBe('https://dev.azure.com/myorg/myrepo/_git/myrepo?path=/path/file.ts&branch=master') + }) + + it('handles Azure repo urls with https', () => { + expect( + getRemoteFileUrl('/path/file.ts', 'https://myorg@dev.azure.com/myorg/myrepo/_git/myrepo'), + ).toBe('https://dev.azure.com/myorg/myrepo/_git/myrepo?path=/path/file.ts&branch=master') + }) }) }) diff --git a/cli/src/connect/__test__/validation.test.ts b/cli/src/connect/__test__/validation.test.ts new file mode 100644 index 0000000..191aad5 --- /dev/null +++ b/cli/src/connect/__test__/validation.test.ts @@ -0,0 +1,81 @@ +import { validateDoc } from '../validation' +import { CodeConnectJSON } from '../figma_connect' +import { logger } from '../../common/logging' + +const MOCK_DOC: any = { + variant: { toggled: 'on' }, +} + +const createMockFigmaNode = (variantOptions: string[]) => { + return { + document: { + type: 'COMPONENT_SET', + componentPropertyDefinitions: { + toggled: { + type: 'VARIANT', + defaultValue: variantOptions[0], + variantOptions, + }, + }, + }, + components: {}, + componentSets: { '1:23': { name: 'ComponentSet' } }, + } +} + +const NID = '1:23' +describe('validateDoc', () => { + describe('variants in a component set', () => { + beforeEach(() => { + logger.error = jest.fn() + jest.resetAllMocks() + }) + it('a variant value should be one of the available variant options', () => { + const figmaNode = createMockFigmaNode(['a', 'b', 'c']) + expect(validateDoc({ variant: { toggled: 'a' } } as any, figmaNode, NID)).toBe(true) + expect(validateDoc({ variant: { toggled: 'b' } } as any, figmaNode, NID)).toBe(true) + expect(validateDoc({ variant: { toggled: 'c' } } as any, figmaNode, NID)).toBe(true) + + expect(validateDoc({ variant: { toggled: 'd' } } as any, figmaNode, NID)).toBe(false) + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining('The Figma Variant "toggled" does not have an option for d'), + ) + }) + + describe('boolean-like variants', () => { + it.each([ + ['true', 'false'], + ['yes', 'no'], + ['on', 'off'], + ['True', 'False'], + ['YES', 'NO'], + ['ON', 'OFF'], + ])('works well with %s/%s variants or exact string matching', (trueValue, falseValue) => { + const figmaNode = createMockFigmaNode([trueValue, falseValue]) + + expect(validateDoc({ variant: { toggled: true } } as any, figmaNode, NID)).toBe(true) + expect(validateDoc({ variant: { toggled: false } } as any, figmaNode, NID)).toBe(true) + expect(validateDoc({ variant: { toggled: trueValue } } as any, figmaNode, NID)).toBe(true) + expect(validateDoc({ variant: { toggled: falseValue } } as any, figmaNode, NID)).toBe(true) + }) + + it("it doesn't work with other values", () => { + const figmaNode = createMockFigmaNode(['positive', 'negative']) + expect(validateDoc({ variant: { toggled: true } } as any, figmaNode, NID)).toBe(false) + expect(validateDoc({ variant: { toggled: false } } as any, figmaNode, NID)).toBe(false) + }) + it("it doesn't work with more than two variant values", () => { + const figmaNode = createMockFigmaNode(['true', 'false', 'maybe']) + expect(validateDoc({ variant: { toggled: true } } as any, figmaNode, NID)).toBe(false) + expect(validateDoc({ variant: { toggled: false } } as any, figmaNode, NID)).toBe(false) + }) + it("it doesn't work with using a non-boolean value with boolean-like variants", () => { + const figmaNode = createMockFigmaNode(['true', 'false']) + expect(validateDoc({ variant: { toggled: 1 } } as any, figmaNode, NID)).toBe(false) + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining('The Figma Variant "toggled" does not have an option for 1'), + ) + }) + }) + }) +}) diff --git a/cli/src/connect/create.ts b/cli/src/connect/create.ts index bf635e4..3feb212 100644 --- a/cli/src/connect/create.ts +++ b/cli/src/connect/create.ts @@ -11,7 +11,11 @@ import { callParser, handleMessages } from './parser_executables' import { CodeConnectExecutableParserConfig, ProjectInfo } from './project' import { createReactCodeConnect } from '../react/create' import { z } from 'zod' -import { CreateRequestPayload, CreateResponsePayload } from './parser_executable_types' +import { + CreateRequestPayload, + CreateRequestPayloadMulti, + CreateResponsePayload, +} from './parser_executable_types' import { fromError } from 'zod-validation-error' import { createHtmlCodeConnect } from '../html/create' import { isFetchError, request } from '../common/fetch' @@ -77,19 +81,13 @@ export async function createCodeConnectFromUrl({ } const normalizedName = normalizeComponentName(component.name) - const payload: CreateRequestPayload = { - mode: 'CREATE', - destinationDir: outDir ?? process.env.INIT_CWD ?? process.cwd(), - destinationFile: outFile, - component: { - figmaNodeUrl, - id: component.id, - name: component.name, - normalizedName, - type: component.type, - componentPropertyDefinitions: component.componentPropertyDefinitions, - }, - config: projectInfo.config, + const componentPayload = { + figmaNodeUrl, + id: component.id, + name: component.name, + normalizedName, + type: component.type, + componentPropertyDefinitions: component.componentPropertyDefinitions, } logger.info('Generating Code Connect files...') @@ -97,11 +95,34 @@ export async function createCodeConnectFromUrl({ let result: z.infer if (projectInfo.config.parser === 'react') { + const payload: CreateRequestPayloadMulti = { + mode: 'CREATE', + destinationDir: outDir ?? process.env.INIT_CWD ?? process.cwd(), + destinationFile: outFile, + normalizedName, + figmaConnections: [{ component: componentPayload }], + config: projectInfo.config, + } result = await createReactCodeConnect(payload) } else if (projectInfo.config.parser === 'html') { + const payload: CreateRequestPayloadMulti = { + mode: 'CREATE', + destinationDir: outDir ?? process.env.INIT_CWD ?? process.cwd(), + destinationFile: outFile, + normalizedName, + figmaConnections: [{ component: componentPayload }], + config: projectInfo.config, + } result = await createHtmlCodeConnect(payload) } else { try { + const payload: CreateRequestPayload = { + mode: 'CREATE', + destinationDir: outDir ?? process.env.INIT_CWD ?? process.cwd(), + destinationFile: outFile, + component: componentPayload, + config: projectInfo.config, + } const stdout = await callParser( // We use `as` because the React parser makes the types difficult // TODO remove once React is an executable parser diff --git a/cli/src/connect/figma_rest_api.ts b/cli/src/connect/figma_rest_api.ts index 6471898..bab1c9b 100644 --- a/cli/src/connect/figma_rest_api.ts +++ b/cli/src/connect/figma_rest_api.ts @@ -39,13 +39,18 @@ export namespace FigmaRestApi { export interface Node { // we don't care about other node types - type: 'COMPONENT' | 'COMPONENT_SET' | 'OTHER' + type: 'COMPONENT' | 'COMPONENT_SET' | 'OTHER' | 'CANVAS' name: string id: string children: Node[] } - export interface Component extends Node { + export interface NodeWithPageInfo extends Node { + pageId: string + pageName: string + } + + export interface Component extends NodeWithPageInfo { type: 'COMPONENT' | 'COMPONENT_SET' componentPropertyDefinitions: Record } diff --git a/cli/src/connect/helpers.ts b/cli/src/connect/helpers.ts index 2fcff20..23df73a 100644 --- a/cli/src/connect/helpers.ts +++ b/cli/src/connect/helpers.ts @@ -50,22 +50,32 @@ export function findComponentsInDocument( nodeIds?: string[], ): FigmaRestApi.Component[] { const components: FigmaRestApi.Component[] = [] - const stack = [document] + const pages: FigmaRestApi.Node[] = document.children - while (stack.length > 0) { - const node = stack.pop()! - if (nodeIds && nodeIds.includes(node.id)) { - if (!isComponent(node)) { - throw new Error('Specified node is not a component or a component set') - } - components.push(node) - } - if (!nodeIds && isComponent(node)) { - components.push(node) + for (const page of pages) { + if (page.type !== 'CANVAS') { + continue } - // don't traverse into component sets - if (Array.isArray(node.children) && !isComponent(node)) { - stack.push(...node.children) + const pageId = page.id + const pageName = page.name + + const stack: FigmaRestApi.Node[] = page.children + + while (stack.length > 0) { + const node = stack.pop()! + if (nodeIds && nodeIds.includes(node.id)) { + if (!isComponent(node)) { + throw new Error('Specified node is not a component or a component set') + } + components.push({ ...node, pageId, pageName }) + } + if (!nodeIds && isComponent(node)) { + components.push({ ...node, pageId, pageName }) + } + // don't traverse into component sets + if (Array.isArray(node.children) && !isComponent(node)) { + stack.push(...node.children) + } } } diff --git a/cli/src/connect/intrinsics.ts b/cli/src/connect/intrinsics.ts index 99e8c28..c4973a9 100644 --- a/cli/src/connect/intrinsics.ts +++ b/cli/src/connect/intrinsics.ts @@ -3,6 +3,7 @@ import { InternalError, ParserContext, ParserError } from './parser_common' import { assertIsArrayLiteralExpression, assertIsStringLiteral, + convertArrayLiteralToJs, isUndefinedType, stripQuotesFromNode, } from '../typescript/compiler' @@ -11,6 +12,7 @@ import { assertIsObjectLiteralExpression } from '../typescript/compiler' import { FigmaConnectAPI } from './api' import { FCCValue, + _fcc_array, _fcc_function, _fcc_identifier, _fcc_jsxElement, @@ -470,6 +472,8 @@ export function valueToString(value: ValueMappingKind, childLayer?: string) { return `_fcc_templateString('${v}')` case 'jsx-element': return `_fcc_jsxElement('${v}')` + case 'array': + return `_fcc_array(${v})` default: throw new InternalError(`Unknown helper type: ${value}`) } @@ -571,6 +575,8 @@ function expressionToFccEnumValue( valueNode: ts.Expression, parserContext: ParserContext, ): FCCValue { + const { sourceFile, checker } = parserContext + if (ts.isParenthesizedExpression(valueNode)) { return expressionToFccEnumValue(valueNode.expression, parserContext) } @@ -588,6 +594,17 @@ function expressionToFccEnumValue( return _fcc_object(parsePropsObject(valueNode, parserContext)) } + if (ts.isArrayLiteralExpression(valueNode)) { + return _fcc_array( + convertArrayLiteralToJs(valueNode, sourceFile, checker, (valueNode) => { + if (ts.isCallExpression(valueNode)) { + return parseIntrinsic(valueNode, parserContext) + } + return expressionToFccEnumValue(valueNode, parserContext) + }), + ) + } + if (ts.isTemplateLiteral(valueNode)) { const str = valueNode.getText().replaceAll('`', '') return _fcc_templateString(str) diff --git a/cli/src/connect/parser_common.ts b/cli/src/connect/parser_common.ts index ee05304..4d57555 100644 --- a/cli/src/connect/parser_common.ts +++ b/cli/src/connect/parser_common.ts @@ -259,9 +259,21 @@ export function getReferencedPropsForTemplate({ /** * Checks if a file contains Code Connect by looking for the `figma.connect()` function call */ -export function isFigmaConnectFile(program: ts.Program, file: string, extension: string) { - // We don't support Code Connect in JSX and this throws an error if we let it proceed - if (!file.endsWith(`.${extension}`)) { +export function isFigmaConnectFile( + program: ts.Program, + file: string, + extension: string | string[], +) { + const allowedExtensions = Array.isArray(extension) ? extension : [extension] + const fileExtension = file.split('.').pop() + + // If the file has no extension, we can't determine if it's a Code Connect file + if (!fileExtension) { + return false + } + + // If the file extension is not in the list of supported extensions, it's not a Code Connect file + if (!allowedExtensions.includes(fileExtension)) { return false } diff --git a/cli/src/connect/parser_executable_types.ts b/cli/src/connect/parser_executable_types.ts index ed4b220..3c2cbf4 100644 --- a/cli/src/connect/parser_executable_types.ts +++ b/cli/src/connect/parser_executable_types.ts @@ -126,6 +126,32 @@ export type ComponentPropertyDefinition = { variantOptions?: string[] } +export interface FigmaConnectionComponent { + // The URL of the Figma component. This field is not in the REST API but + // is added for convenience. + figmaNodeUrl: string + // The ID of the Figma component + id: string + // The name of the Figma component + name: string + // The type of the Figma component + type: 'COMPONENT' | 'COMPONENT_SET' + // Map of the Figma component's properties, keyed by property name + componentPropertyDefinitions: Record +} +export interface FigmaConnection { + // The export to use from sourceFilepath + sourceExport?: string + // A mapping of how Figma props should map to code properties + propMapping?: PropMapping + // The type signature for the component (React only) + reactTypeSignature?: ComponentTypeSignature + // Information about the Figma component. This matches the REST API (except the + // figmaNodeUrl and normalizedName fields), which should make it easier to + // implement and maintain as we can just pass it through + component: FigmaConnectionComponent +} + export type CreateRequestPayload = { mode: 'CREATE' // Absolute destination directory for the created file. The parser is free to @@ -169,6 +195,30 @@ export type CreateRequestPayload = { config: Record } +export type CreateRequestPayloadMulti = { + mode: 'CREATE' + // Absolute destination directory for the created file. The parser is free to + // write to a different directory if appropriate (e.g. it analyses your codebase + // to identify where this component should go), but usually it should respect this. + destinationDir: string + // Optional destination file name. If omitted, the parser can determine the + // file name itself. + destinationFile?: string + // The filepath of the code to be connected. If present, this is used instead of + // component.normalizedName + sourceFilepath?: string + // The name of the Figma component, nomalized for use in code. + // This field is not in the REST API but is added for convenience. + normalizedName: string + // The list of design-matchings this file holds + // there can be multiple exports per file, each matching to a different design + figmaConnections: FigmaConnection[] + // The configuration object for this parser. + // Each parser's configuration is separate and can take any shape, though we + // will recommend using the same naming for common concepts like "importPaths". + config: Record +} + export const CreateResponsePayload = z.object({ // A list of files created, which can be output to the console createdFiles: z.array( diff --git a/cli/src/connect/parser_executables.ts b/cli/src/connect/parser_executables.ts index 2328096..12b5a2b 100644 --- a/cli/src/connect/parser_executables.ts +++ b/cli/src/connect/parser_executables.ts @@ -146,6 +146,11 @@ export async function callParser( } if (parser.temporaryIOFilePath) { fs.unlinkSync(parser.temporaryIOFilePath) + // Delete parent directory if empty after removing temp file + const parentDir = path.dirname(parser.temporaryIOFilePath) + if (fs.readdirSync(parentDir).length === 0) { + fs.rmdirSync(parentDir) + } } }) diff --git a/cli/src/connect/project.ts b/cli/src/connect/project.ts index 8dd83bb..8cf4f88 100644 --- a/cli/src/connect/project.ts +++ b/cli/src/connect/project.ts @@ -11,6 +11,7 @@ import findUp from 'find-up' import { exitWithFeedbackMessage } from './helpers' const DEFAULT_CONFIG_FILE_NAME = 'figma.config.json' +const ENV_FILE_NAME = '.env' export const DEFAULT_INCLUDE_GLOBS_BY_PARSER = { react: [`**/*.{tsx,jsx}`], @@ -22,6 +23,11 @@ export const DEFAULT_INCLUDE_GLOBS_BY_PARSER = { __unit_test__: [''], } +export const DEFAULT_LABEL_PER_PARSER: Partial> = { + react: 'React', + html: 'Web Components', +} + // First party parsers which call into parser executables export type CodeConnectExecutableParser = 'swift' | 'compose' | 'custom' | '__unit_test__' @@ -147,7 +153,8 @@ function showParserMessage(message: string) { function packageJsonContains(packageJson: any, dependency: string) { return ( (packageJson.dependencies && packageJson.dependencies[dependency]) || - (packageJson.peerDependencies && packageJson.peerDependencies[dependency]) + (packageJson.peerDependencies && packageJson.peerDependencies[dependency]) || + (packageJson.devDependencies && packageJson.devDependencies[dependency]) ) } @@ -454,7 +461,7 @@ export function getGitRepoDefaultBranchName(repoPath: string) { /** * Finds the URL of a remote file * @param filePath absolute file path on disk - * @param repoURL remote URL + * @param repoURL remote URL, can be a GitHub, GitLab, Bitbucket, etc. URL. * @returns */ export function getRemoteFileUrl(filePath: string, repoURL?: string) { @@ -486,7 +493,28 @@ export function getRemoteFileUrl(filePath: string, repoURL?: string) { const relativeFilePath = filePath.substring(index + repoAbsPath.length) - return `${url}/blob/${defaultBranch}${relativeFilePath}` + if (url.includes('github.com')) { + return `${url}/blob/${defaultBranch}${relativeFilePath}` + } else if (url.includes('gitlab.com')) { + return `${url}/-/blob/${defaultBranch}${relativeFilePath}` + } else if (url.includes('bitbucket.org')) { + return `${url}/src/${defaultBranch}${relativeFilePath}` + } else if (url.includes('dev.azure.com')) { + // `git config --get remote.origin.url` for azure repos will return different strings depending on if it was + // cloned with https or ssh. We need to convert this to a valid URL like "https://dev.azure.com/org/repo/_git/repo?path=/" + if (repoURL.startsWith('git@')) { + // ssh: "git@ssh.dev.azure.com:v3/org/repo/repo" + const [org, project1, project2] = repoURL.split('/').slice(-3) + return `https://dev.azure.com/${org}/${project1}/_git/${project2}?path=${relativeFilePath}&branch=${defaultBranch}` + } else { + // https: "https://org@dev.azure.com/org/repo/_git/repo" + const [_, url] = repoURL.split('@') + return `https://${url}?path=${relativeFilePath}&branch=${defaultBranch}` + } + } else { + logger.error('Unsupported remote URL', url) + return '' + } } export function getStorybookUrl(filePath: string, storybookUrl: string) { @@ -546,6 +574,10 @@ export function getDefaultConfigPath(dir: string) { return path.resolve(path.join(dir, DEFAULT_CONFIG_FILE_NAME)) } +export function getEnvPath(dir: string) { + return path.resolve(path.join(dir, ENV_FILE_NAME)) +} + export async function parseOrDetermineConfig(dir: string, configPath: string) { const configFilePath = configPath ? path.resolve(configPath) : getDefaultConfigPath(dir) @@ -589,6 +621,47 @@ export async function parseOrDetermineConfig(dir: string, configPath: string) { } } +/** + * Check if a .env file exists in the provided directory and if it contains a FIGMA_ACCESS_TOKEN. + */ +export async function checkForEnvAndToken(dir: string) { + // Scan the provided directory for a .env file + const envPath = await findUp('.env', { cwd: dir }) + + if (!envPath) { + // No .env file found + return { + hasEnvFile: false, + envHasFigmaToken: false, + } + } + + // Read the contents of the .env file + const envContents = fs.readFileSync(envPath, 'utf-8') + + // Determine if the .env file contains a FIGMA_ACCESS_TOKEN + const envVars = envContents.split('\n').reduce( + ( + acc: { + [key: string]: string + }, + line, + ) => { + const [key, value] = line.split('=') + acc[key] = value + return acc + }, + {}, + ) + + const figmaAccessToken = envVars['FIGMA_ACCESS_TOKEN'] + + return { + hasEnvFile: true, + envHasFigmaToken: !!figmaAccessToken, + } +} + /** * Gets information about a project from config. * diff --git a/cli/src/connect/validation.ts b/cli/src/connect/validation.ts index 93987d2..5db8b22 100644 --- a/cli/src/connect/validation.ts +++ b/cli/src/connect/validation.ts @@ -153,6 +153,29 @@ function propMatches( return figmaPropName === codeConnectPropName } + + + +export const STATE_BOOLEAN_VALUE_PAIRS = [ + ['yes', 'no'], + ['true', 'false'], + ['on', 'off'], +] + +function isVariantBoolean(variantPossibleValues: string[]) { + if (variantPossibleValues.length === 2) { + const lowerCaseOptions = variantPossibleValues.map((p) => p.toLowerCase()) + for (const pair of STATE_BOOLEAN_VALUE_PAIRS) { + const i = lowerCaseOptions.indexOf(pair[0]!) + const j = lowerCaseOptions.indexOf(pair[1]!) + if (i !== -1 && j !== -1) { + return true + } + } + } + return false +} + function validateVariantRestrictions(doc: CodeConnectJSON, document: any): boolean { if (doc.variant) { let variantRestrictionsValid = true @@ -177,10 +200,15 @@ function validateVariantRestrictions(doc: CodeConnectJSON, document: any): boole // Only check `variantOptions` for Variants, and not for props, since props // don't have a set of possible values we can check against - if ( - variantOrProp.type === 'VARIANT' && - !variantOrProp.variantOptions?.includes(variantRestrictionValue) - ) { + const isValidBooleanVariant = + typeof variantRestrictionValue === 'boolean' && + Array.isArray(variantOrProp.variantOptions) && + isVariantBoolean(variantOrProp.variantOptions) + + const isValidVariantValue = + variantOrProp.variantOptions?.includes(variantRestrictionValue) || isValidBooleanVariant + + if (variantOrProp.type === 'VARIANT' && !isValidVariantValue) { logger.error( `Validation failed for ${doc.component} (${doc.figmaNode}): The Figma Variant "${match}" does not have an option for ${variantRestrictionValue}`, ) @@ -196,7 +224,7 @@ function validateVariantRestrictions(doc: CodeConnectJSON, document: any): boole return true } -function validateDoc(doc: CodeConnectJSON, figmaNode: any, nodeId: string): boolean { +export function validateDoc(doc: CodeConnectJSON, figmaNode: any, nodeId: string): boolean { if (!figmaNode || !figmaNode.document) { logger.error( `Validation failed for ${doc.component} (${doc.figmaNode}): node not found in file`, diff --git a/cli/src/connect/wizard/__test__/prop_mapping/prop_mapping_helpers.test.ts b/cli/src/connect/wizard/__test__/prop_mapping/prop_mapping_helpers.test.ts index fe1de19..b267250 100644 --- a/cli/src/connect/wizard/__test__/prop_mapping/prop_mapping_helpers.test.ts +++ b/cli/src/connect/wizard/__test__/prop_mapping/prop_mapping_helpers.test.ts @@ -68,6 +68,8 @@ describe('Prop mapping helpers', () => { type: 'COMPONENT', name: 'My component', id: '123:123', + pageId: '0:1', + pageName: 'Page 1', children: [], componentPropertyDefinitions: { 'Has Icon': { @@ -130,6 +132,8 @@ describe('Prop mapping helpers', () => { type: 'COMPONENT', name: 'My component', id: '123:123', + pageId: '0:1', + pageName: 'Page 1', children: [], componentPropertyDefinitions: { 'Has Icon': { diff --git a/cli/src/connect/wizard/__test__/run_wizard.test.ts b/cli/src/connect/wizard/__test__/run_wizard.test.ts index b219031..67887c4 100644 --- a/cli/src/connect/wizard/__test__/run_wizard.test.ts +++ b/cli/src/connect/wizard/__test__/run_wizard.test.ts @@ -4,10 +4,15 @@ import { FigmaRestApi } from '../../figma_rest_api' import * as project from '../../project' import { convertRemoteFileUrlToRelativePath, + createCodeConnectFiles, getComponentChoicesForPrompt, getUnconnectedComponentsAndConnectedComponentMappings, } from '../run_wizard' +jest.mock('fs') + +import fs from 'fs' + const _stripAnsi = require('strip-ansi') const MOCK_COMPONENTS: FigmaRestApi.Component[] = [ @@ -17,6 +22,8 @@ const MOCK_COMPONENTS: FigmaRestApi.Component[] = [ id: '1:11', children: [], componentPropertyDefinitions: {}, + pageId: '0:1', + pageName: 'Page 1', }, { type: 'COMPONENT', @@ -24,6 +31,8 @@ const MOCK_COMPONENTS: FigmaRestApi.Component[] = [ id: '1:12', children: [], componentPropertyDefinitions: {}, + pageId: '0:1', + pageName: 'Page 1', }, { type: 'COMPONENT', @@ -31,9 +40,25 @@ const MOCK_COMPONENTS: FigmaRestApi.Component[] = [ id: '1:13', children: [], componentPropertyDefinitions: {}, + pageId: '0:1', + pageName: 'Page 1', }, ] +const CREATE_CODE_CONNECT_FILES_PAYLOAD = { + figmaFileUrl: 'https://www.figma.com/file/1234567890/My-Awesome-Design', + linkedNodeIdsToFilepathExports: {}, + unconnectedComponentsMap: { + '1:11': MOCK_COMPONENTS[0], + '1:12': MOCK_COMPONENTS[1], + '1:13': MOCK_COMPONENTS[2], + }, + outDir: null, + projectInfo: { config: { include: ['/**/*.{tsx,jsx}'], parser: 'react' } }, + cmd: {}, + accessToken: '', + useAi: false, +} describe('getComponentChoicesForPrompt', () => { it('returns a sorted list of linked + unlinked formatted choices', () => { const result = getComponentChoicesForPrompt( @@ -46,9 +71,9 @@ describe('getComponentChoicesForPrompt', () => { ) expect(result.map((r) => _stripAnsi(r.title))).toEqual([ - `Figma component: another component ↔️ ${path.join('some', 'component', 'path.tsx')}`, - `Figma component: a reeeeeeeally long component name ↔️ -`, - `Figma component: different component ↔️ -`, + `Figma component: another component ↔️ Code Definition: ${path.join('some', 'component', 'path.tsx')}`, + `Figma component: a reeeeeeeally long component name ↔️ Code Definition: -`, + `Figma component: different component ↔️ Code Definition: -`, ]) }) @@ -63,9 +88,9 @@ describe('getComponentChoicesForPrompt', () => { ) expect(result.map((r) => _stripAnsi(r.title))).toEqual([ - `Figma component: another component ↔️ ${path.join('component', 'path.tsx')}`, - `Figma component: a reeeeeeeally long component name ↔️ -`, - `Figma component: different component ↔️ -`, + `Figma component: another component ↔️ Code Definition: ${path.join('component', 'path.tsx')}`, + `Figma component: a reeeeeeeally long component name ↔️ Code Definition: -`, + `Figma component: different component ↔️ Code Definition: -`, ]) }) @@ -86,9 +111,9 @@ describe('getComponentChoicesForPrompt', () => { '/', ) expect(result.map((r) => _stripAnsi(r.title))).toEqual([ - `Figma component: a reeeeeeeally long component name ↔️ -`, - `Figma component: some connected component ↔️ /foo/connectedComponent1.tsx`, - `Figma component: another connected component ↔️ /foo/connectedComponent2.tsx`, + `Figma component: a reeeeeeeally long component name ↔️ Code Definition: -`, + `Figma component: some connected component ↔️ Code Definition: /foo/connectedComponent1.tsx`, + `Figma component: another connected component ↔️ Code Definition: /foo/connectedComponent2.tsx`, ]) }) }) @@ -140,12 +165,16 @@ describe('getUnconnectedComponentsAndConnectedComponentMappings', () => { name: 'another component', id: '1:12', children: [], + pageId: '0:1', + pageName: 'Page 1', componentPropertyDefinitions: {}, }, { type: 'COMPONENT', name: 'different component', id: '1:13', + pageId: '0:1', + pageName: 'Page 1', children: [], componentPropertyDefinitions: {}, }, @@ -168,3 +197,88 @@ describe('convertRemoteFileUrlToRelativePath', () => { expect(result).toBe(path.join('ds', 'Modal.tsx')) }) }) + +describe('createCodeConnectFiles', () => { + const destinationRegex = /(.+)project(.+)src(.+)Component\.figma\.tsx/ + beforeEach(() => { + fs.mkdirSync = jest.fn() + fs.writeFileSync = jest.fn() + fs.existsSync = jest.fn().mockReturnValue(false) + }) + + it('creates all code connect files - 2 files', async () => { + const result = await createCodeConnectFiles({ + ...CREATE_CODE_CONNECT_FILES_PAYLOAD, + linkedNodeIdsToFilepathExports: { + '1:11': '/project/src/Component.tsx~Component', + '1:12': '/project/src/AnotherComponent.tsx~AnotherComponent', + }, + } as any) + + expect(fs.writeFileSync).toHaveBeenCalledTimes(2) + expect(result).toBe(true) + }) + it('creates all code connect files - 3 files', async () => { + const result = await createCodeConnectFiles({ + ...CREATE_CODE_CONNECT_FILES_PAYLOAD, + linkedNodeIdsToFilepathExports: { + '1:11': '/project/src/Component.tsx~Component', + '1:12': '/project/src/AnotherComponent.tsx~AnotherComponent', + '1:13': '/project/src/DifferentComponent.tsx~DifferentComponent', + }, + } as any) + + expect(fs.writeFileSync).toHaveBeenCalledTimes(3) + expect(result).toBe(true) + }) + + it('skips creation of existing files', async () => { + fs.existsSync = jest.fn().mockReturnValue(true) + const result = await createCodeConnectFiles({ + ...CREATE_CODE_CONNECT_FILES_PAYLOAD, + linkedNodeIdsToFilepathExports: { + '1:11': '/project/src/Component.tsx~Component', + }, + } as any) + expect(result).toBe(false) + expect(fs.writeFileSync).toHaveBeenCalledTimes(0) + }) + + it('creates files with default export', async () => { + await createCodeConnectFiles({ + ...CREATE_CODE_CONNECT_FILES_PAYLOAD, + linkedNodeIdsToFilepathExports: { + '1:11': '/project/src/Component.tsx~default', + }, + } as any) + expect(fs.writeFileSync).toHaveBeenCalledWith( + expect.stringMatching(destinationRegex), + expect.stringContaining('import Component from "./Component"'), + ) + }) + it('creates files with named export', async () => { + await createCodeConnectFiles({ + ...CREATE_CODE_CONNECT_FILES_PAYLOAD, + linkedNodeIdsToFilepathExports: { + '1:11': '/project/src/Component.tsx~Component', + }, + } as any) + expect(fs.writeFileSync).toHaveBeenCalledWith( + expect.stringMatching(destinationRegex), + expect.stringContaining('import { Component } from "./Component"'), + ) + }) + it('creates files with named and default export', async () => { + await createCodeConnectFiles({ + ...CREATE_CODE_CONNECT_FILES_PAYLOAD, + linkedNodeIdsToFilepathExports: { + '1:11': '/project/src/Component.tsx~Component', + '1:12': '/project/src/Component.tsx~default', + }, + } as any) + expect(fs.writeFileSync).toHaveBeenCalledWith( + expect.stringMatching(destinationRegex), + expect.stringContaining('import ComponentDefault, { Component } from "./Component"'), + ) + }) +}) diff --git a/cli/src/connect/wizard/helpers.ts b/cli/src/connect/wizard/helpers.ts index 7e432d4..9784088 100644 --- a/cli/src/connect/wizard/helpers.ts +++ b/cli/src/connect/wizard/helpers.ts @@ -2,10 +2,13 @@ import * as prettier from 'prettier' import fs from 'fs' import { CodeConnectConfig, + CodeConnectParser, DEFAULT_INCLUDE_GLOBS_BY_PARSER, + DEFAULT_LABEL_PER_PARSER, ProjectInfo, ReactProjectInfo, getDefaultConfigPath, + getEnvPath, } from '../project' import { exitWithError, logger, success } from '../../common/logging' import path from 'path' @@ -52,6 +55,19 @@ export function getIncludesGlob({ return DEFAULT_INCLUDE_GLOBS_BY_PARSER[config.parser] } +export async function createEnvFile({ dir, accessToken }: { dir: string; accessToken?: string }) { + // Create .env file + const filePath = getDefaultConfigPath(dir) + fs.writeFileSync(getEnvPath(dir), `FIGMA_ACCESS_TOKEN="${accessToken}"`) + logger.info(success(`Created ${filePath}`)) +} + +export function addTokenToEnvFile({ dir, accessToken }: { dir: string; accessToken?: string }) { + const filePath = getDefaultConfigPath(dir) + fs.appendFileSync(getEnvPath(dir), `\nFIGMA_ACCESS_TOKEN="${accessToken}"`) + logger.info(success(`Appended access token to ${filePath}`)) +} + export async function createCodeConnectConfig({ dir, componentDirectory, @@ -61,14 +77,22 @@ export async function createCodeConnectConfig({ componentDirectory: string | null config: CodeConnectConfig }) { + const label = DEFAULT_LABEL_PER_PARSER[config.parser as CodeConnectParser] const includesGlob = getIncludesGlob({ dir, componentDirectory, config }) - const configJson = ` -{ + + const configJson = label + ? `{ + "codeConnect": { + "include": ["${includesGlob}"], + "label": "${label}" + } +}` + : `{ "codeConnect": { "include": ["${includesGlob}"] } -} - ` +}` + const formatted = await prettier.format(configJson, { parser: 'json', }) diff --git a/cli/src/connect/wizard/run_wizard.ts b/cli/src/connect/wizard/run_wizard.ts index d3e9aa8..0126701 100644 --- a/cli/src/connect/wizard/run_wizard.ts +++ b/cli/src/connect/wizard/run_wizard.ts @@ -13,11 +13,18 @@ import { CodeConnectConfig, ProjectInfo, CodeConnectExecutableParserConfig, + checkForEnvAndToken, } from '../../connect/project' import { parseFigmaNode } from '../validation' import chalk from 'chalk' import path from 'path' -import { CreateRequestPayload, CreateResponsePayload } from '../parser_executable_types' +import { + CreateRequestPayload, + CreateRequestPayloadMulti, + CreateResponsePayload, + FigmaConnection, + PropMapping, +} from '../parser_executable_types' import { normalizeComponentName } from '../create' import { createReactCodeConnect } from '../../react/create' import { CodeConnectJSON } from '../../connect/figma_connect' @@ -29,6 +36,8 @@ import { parseFilepathExport, getIncludesGlob, isValidFigmaUrl, + createEnvFile, + addTokenToEnvFile, } from './helpers' import stripAnsi from 'strip-ansi' import { callParser, handleMessages } from '../parser_executables' @@ -38,10 +47,12 @@ import { fromError } from 'zod-validation-error' import { autoLinkComponents } from './autolinking' import { extractDataAndGenerateAllPropsMappings } from './prop_mapping_helpers' import { isFetchError, request } from '../../common/fetch' +import { ComponentTypeSignature } from '../../react/parser' type ConnectedComponentMappings = { componentName: string; filepathExport: string }[] const NONE = '(None)' +const MAX_COMPONENTS_TO_MAP = 40 function clearQuestion(prompt: prompts.PromptObject, answer: string) { const displayedAnswer = @@ -129,6 +140,57 @@ async function fetchTopLevelComponentsFromFile({ } } +/** + * enable selection of a subset of components per page + * @param components to narrow down from + * @returns components narrowed down to the selected pages + */ +export async function narrowDownComponentsPerPage( + components: FigmaRestApi.Component[], + pages: Record, +) { + const createTitle = (name: string, id: string, pad: number) => { + const componentsInPage = components.filter((c) => c.pageId === id) + let namesPreview = componentsInPage + .slice(0, 4) + .map((c) => c.name) + .join(', ') + + namesPreview = componentsInPage.length > 4 ? `${namesPreview}, ...` : `${namesPreview}` + const componentWord = componentsInPage.length === 1 ? 'Component' : 'Components' + + return `${name.padEnd(pad, ' ')} - ${componentsInPage.length} ${componentWord} ${chalk.dim( + `(${namesPreview})`, + )}` + } + + const longestPageName = Math.max(...Object.values(pages).map((name) => name.length)) + + let pagesToMap: string[] = [] + console.info('') + while (true) { + const { pagesToMap_temp } = await askQuestion({ + type: 'multiselect', + name: 'pagesToMap_temp', + message: `Select the pages with the Figma components you'd like to map (Press ${chalk.green('space')} to select and ${chalk.green('enter')} to continue)`, + instructions: false, + choices: Object.entries(pages).map(([id, name]) => ({ + title: createTitle(name, id, longestPageName), + value: id, + })), + }) + + if (!pagesToMap_temp || pagesToMap_temp.length === 0) { + logger.warn('Select at least one page to continue.') + continue + } + pagesToMap = pagesToMap_temp + break + } + + return components.filter((c) => pagesToMap.includes(c.pageId)) +} + /** * Asks a Prompts question and adds spacing * @param question Prompts question @@ -196,8 +258,13 @@ async function askQuestionWithExitConfirmation( } function formatComponentTitle(componentName: string, filepathExport: string | null, pad: number) { + const fileExport = filepathExport ? parseFilepathExport(filepathExport) : null const nameLabel = `${chalk.dim('Figma component:')} ${componentName.padEnd(pad, ' ')}` - const linkedLabel = `↔️ ${filepathExport ? parseFilepathExport(filepathExport).filepath : '-'}` + + const filepathLabel = fileExport ? fileExport.filepath : '-' + const exportNote = fileExport?.exportName ? ` (${fileExport.exportName})` : '' + + const linkedLabel = `↔️ ${chalk.dim('Code Definition:')} ${filepathLabel}${exportNote}` return `${nameLabel} ${linkedLabel}` } @@ -233,7 +300,7 @@ export function getComponentChoicesForPrompt( return { title: formatComponentTitle(c.name, filepathExport, longestNameLength), value: c.id, - description: `${chalk.green('Edit link')}`, + description: `${chalk.green('Edit Match')}`, } } @@ -274,6 +341,24 @@ type ManualLinkingArgs = { cmd: BaseCommand } +const escapeHandler = () => { + let escPressed = false + const keypressListener = (_: any, key: { name: string }) => { + if (key.name === 'escape') { + escPressed = true + } + } + return { + escPressed: () => escPressed, + start: () => { + process.stdin.on('keypress', keypressListener) + }, + stop: () => { + process.stdin.removeListener('keypress', keypressListener) + }, + } +} + async function runManualLinking({ unconnectedComponents, linkedNodeIdsToFilepathExports, @@ -285,11 +370,13 @@ async function runManualLinking({ const dir = getDir(cmd) while (true) { // Don't show exit confirmation as we're relying on esc behavior + const escHandler = escapeHandler() + escHandler.start() const { nodeId } = await prompts( { type: 'select', name: 'nodeId', - message: `Select a link to edit (Press ${chalk.green( + message: `Select a Figma component match you'd like to edit (Press ${chalk.green( 'esc', )} when you're ready to continue on)`, choices: getComponentChoicesForPrompt( @@ -300,11 +387,20 @@ async function runManualLinking({ ), warn: 'This component already has a local Code Connect file.', hint: ' ', + ...({ submitOnEscapeKey: true } as any), }, { - onSubmit: clearQuestion, + onSubmit: (question, answer) => { + if (!escHandler.escPressed()) { + clearQuestion(question, answer) + } + }, }, ) + if (escHandler.escPressed()) { + return + } + if (!nodeId) { return } @@ -338,6 +434,9 @@ async function runManualLinking({ onSubmit: clearQuestion, }, ) + if (escHandler.escPressed()) { + continue + } if (pathToComponent) { if (pathToComponent === NONE) { delete linkedNodeIdsToFilepathExports[nodeId] @@ -363,34 +462,38 @@ async function runManualLinking({ prevSelectedComponent && prevSelectedFilepath === pathToComponent ? fileExports.findIndex(({ title }) => title === prevSelectedComponent) : 0, + ...({ submitOnEscapeKey: true } as any), }, { onSubmit: clearQuestion, }, ) + if (escHandler.escPressed()) { + continue + } linkedNodeIdsToFilepathExports[nodeId] = filepathExport } } } + escHandler.stop() } } async function runManualLinkingWithConfirmation(manualLinkingArgs: ManualLinkingArgs) { let outDir = manualLinkingArgs.cmd.outDir || null - let hasAskedOutDirQuestion = false while (true) { await runManualLinking(manualLinkingArgs) - if (!outDir && !hasAskedOutDirQuestion) { + if (!outDir) { + console.info( + '\nA Code Connect file is created for each Figma component to code definition match.', + ) const { outputDirectory } = await askQuestionWithExitConfirmation({ type: 'text', name: 'outputDirectory', - message: `What directory should Code Connect files be created in? (Press ${chalk.green( - 'enter', - )} to co-locate your files alongside your component files)`, + message: `In which directory would you like to store Code Connect files? (Press ${chalk.green('enter')} to co-locate your files alongside your component files)`, }) - hasAskedOutDirQuestion = true outDir = outputDirectory } @@ -432,14 +535,90 @@ async function runManualLinkingWithConfirmation(manualLinkingArgs: ManualLinking }, ], }) - if (confirmation !== 'backToEdit') { + if (confirmation === 'backToEdit') { + outDir = manualLinkingArgs.cmd.outDir || null + } else { return outDir } } } } -async function createCodeConnectFiles({ +interface AddPayloadArgs { + payloadType: 'MULTI_EXPORT' | 'SINGLE_EXPORT' + filepath: string + sourceExport: string + reactTypeSignature?: ComponentTypeSignature + propMapping?: PropMapping + figmaNodeUrl: string + moreComponentProps: FigmaRestApi.Component + destinationDir: string + sourceFilepath: string + normalizedName: string + config: CodeConnectConfig +} + +export async function addPayload( + payloads: Record, + args: AddPayloadArgs, +) { + const { + payloadType, + filepath, + sourceExport, + reactTypeSignature, + propMapping, + figmaNodeUrl, + moreComponentProps, + destinationDir, + sourceFilepath, + normalizedName, + config, + } = args + + if (payloadType === 'MULTI_EXPORT') { + const figmaConnection: FigmaConnection = { + sourceExport, + reactTypeSignature, + propMapping, + component: { + figmaNodeUrl, + ...moreComponentProps, + }, + } + + if (payloads[filepath]) { + ;(payloads[filepath] as CreateRequestPayloadMulti).figmaConnections.push(figmaConnection) + } else { + const payload: CreateRequestPayloadMulti = { + mode: 'CREATE', + destinationDir, + sourceFilepath, + normalizedName, + figmaConnections: [figmaConnection], + config, + } + payloads[filepath] = payload + } + } + + if (payloadType === 'SINGLE_EXPORT') { + const payload: CreateRequestPayload = { + mode: 'CREATE', + destinationDir, + sourceFilepath, + component: { + figmaNodeUrl, + normalizedName, + ...moreComponentProps, + }, + config, + } + payloads[filepath] = payload + } +} + +export async function createCodeConnectFiles({ linkedNodeIdsToFilepathExports, figmaFileUrl, unconnectedComponentsMap, @@ -492,6 +671,9 @@ async function createCodeConnectFiles({ } let allFilesCreated = true + + const payloads: Record = {} + for (const [nodeId, filepathExport] of Object.entries(linkedNodeIdsToFilepathExports)) { const urlObj = new URL(figmaFileUrl) urlObj.search = '' @@ -502,32 +684,38 @@ async function createCodeConnectFiles({ const outDir = outDirArg || path.dirname(filepath) - const payload: CreateRequestPayload = { - mode: 'CREATE', - destinationDir: outDir, - sourceFilepath: filepath, - sourceExport: exportName || undefined, + const payloadType = + projectInfo.config.parser === 'react' || projectInfo.config.parser === 'html' + ? 'MULTI_EXPORT' + : 'SINGLE_EXPORT' + + addPayload(payloads, { + payloadType, + filepath, + sourceExport: exportName || '?', reactTypeSignature: propMappingsAndData?.propMappingData[filepathExport]?.signature, propMapping: propMappingsAndData?.propMappings[filepathExport], - component: { - figmaNodeUrl: urlObj.toString(), - normalizedName: normalizeComponentName(name), - ...unconnectedComponentsMap[nodeId], - }, + figmaNodeUrl: urlObj.toString(), + moreComponentProps: unconnectedComponentsMap[nodeId], + destinationDir: outDir, + sourceFilepath: filepath, + normalizedName: normalizeComponentName(name), config: projectInfo.config, - } + }) + } + for (const payloadKey of Object.keys(payloads)) { + const payload = payloads[payloadKey] let result: z.infer - if (projectInfo.config.parser === 'react') { - result = await createReactCodeConnect(payload) + result = await createReactCodeConnect(payload as CreateRequestPayloadMulti) } else { try { const stdout = await callParser( // We use `as` because the React parser makes the types difficult // TODO remove once React is an executable parser projectInfo.config as CodeConnectExecutableParserConfig, - payload, + payload as CreateRequestPayload, projectInfo.absPath, ) @@ -733,11 +921,14 @@ export async function runWizard(cmd: BaseCommand) { const dir = getDir(cmd) const { hasConfigFile, config } = await parseOrDetermineConfig(dir, cmd.config) + const { hasEnvFile, envHasFigmaToken } = await checkForEnvAndToken(dir) // This isn't ideal as you see the intro text followed by an error, but we'll // add support for this soon so I think it's OK if (config.parser === 'html') { - exitWithError('HTML projects are currently not supported by Code Connect interactive setup') + exitWithError( + 'HTML projects are currently not supported by Code Connect interactive setup. Please use the "npx figma connect create" command instead.', + ) } let accessToken = getAccessToken(cmd) @@ -755,6 +946,53 @@ export async function runWizard(cmd: BaseCommand) { logger.info('') + if (!hasEnvFile) { + // If there is no .env file, we should ask the user if they want to create one + // to store the Figma token. + + const { createConfigFile } = await askQuestionOrExit({ + type: 'select', + name: 'createConfigFile', + message: + "It looks like you don't have a .env file. Would you like to generate one now to store the Figma access token?", + choices: [ + { + title: 'Yes', + value: 'yes', + }, + { + title: 'No', + value: 'no', + }, + ], + }) + + if (createConfigFile === 'yes') { + await createEnvFile({ dir, accessToken }) + } + } else if (!envHasFigmaToken) { + // If there is a .env file but no Figma token, we should ask the user if they want to add it. + const { addFigmaToken } = await askQuestionOrExit({ + type: 'select', + name: 'addFigmaToken', + message: 'Would you like to add your Figma access token to your .env file?', + choices: [ + { + title: 'Yes', + value: 'yes', + }, + { + title: 'No', + value: 'no', + }, + ], + }) + + if (addFigmaToken === 'yes') { + addTokenToEnvFile({ dir, accessToken }) + } + } + const { componentDirectory, projectInfo, filepathExports } = await askForTopLevelDirectoryOrDetermineFromConfig({ dir, @@ -771,7 +1009,7 @@ export async function runWizard(cmd: BaseCommand) { validate: (value: string) => isValidFigmaUrl(value) || 'Please enter a valid Figma file URL.', }) - const componentsFromFile = await fetchTopLevelComponentsFromFile({ + let componentsFromFile = await fetchTopLevelComponentsFromFile({ accessToken, figmaUrl: figmaFileUrl, cmd, @@ -826,6 +1064,22 @@ export async function runWizard(cmd: BaseCommand) { useAi = useAiSelection === 'yes' } + const pagesFromFile: Record = componentsFromFile.reduce( + (acc, c) => { + acc[c.pageId] = c.pageName + return acc + }, + {} as Record, + ) + const pagesFromFileCount = Object.keys(pagesFromFile).length + + if (componentsFromFile.length > MAX_COMPONENTS_TO_MAP && pagesFromFileCount > 1) { + logger.info( + `${componentsFromFile.length} Figma components found in the Figma file across ${pagesFromFileCount} pages.`, + ) + componentsFromFile = await narrowDownComponentsPerPage(componentsFromFile, pagesFromFile) + } + const linkedNodeIdsToFilepathExports = {} const { unconnectedComponents, connectedComponentsMappings } = @@ -844,7 +1098,7 @@ export async function runWizard(cmd: BaseCommand) { logger.info( boxen( - `${chalk.bold(`Connecting your components`)}\n\n` + + `${chalk.bold(`Connecting your Figma components`)}\n\n` + `${chalk.green( `${chalk.bold(Object.keys(linkedNodeIdsToFilepathExports).length)} ${ Object.keys(linkedNodeIdsToFilepathExports).length === 1 diff --git a/cli/src/html/create.ts b/cli/src/html/create.ts index 03bbd1e..60fd32b 100644 --- a/cli/src/html/create.ts +++ b/cli/src/html/create.ts @@ -1,5 +1,8 @@ import { z } from 'zod' -import { CreateRequestPayload, CreateResponsePayload } from '../connect/parser_executable_types' +import { + CreateResponsePayload, + CreateRequestPayloadMulti, +} from '../connect/parser_executable_types' import path from 'path' import fs from 'fs' import { generateProps } from '../react/create' @@ -8,10 +11,12 @@ import { kebabCase } from 'lodash' import { getOutFileName } from '../connect/create_common' export async function createHtmlCodeConnect( - payload: CreateRequestPayload, + payload: CreateRequestPayloadMulti, ): Promise> { - const { component, destinationFile, destinationDir, sourceFilepath } = payload - const { normalizedName, figmaNodeUrl } = component + const { figmaConnections, normalizedName, destinationFile, destinationDir, sourceFilepath } = + payload + const component = figmaConnections[0].component + const { figmaNodeUrl } = component const webComponentName = kebabCase(normalizedName) diff --git a/cli/src/html/parser.ts b/cli/src/html/parser.ts index 5c4122f..0b80fed 100644 --- a/cli/src/html/parser.ts +++ b/cli/src/html/parser.ts @@ -20,6 +20,7 @@ import { parseImports, ParseOptions, } from '../connect/parser_common' +import { CodeConnectParser, DEFAULT_LABEL_PER_PARSER } from '../connect/project' import { format } from 'prettier' function getHtmlTaggedTemplateNode(node: ts.Node): ts.TaggedTemplateExpression | undefined { @@ -573,7 +574,7 @@ export async function parseHtmlDoc( return { figmaNode, - label: 'Web Components', + label: DEFAULT_LABEL_PER_PARSER.html!, language: 'html', component: metadata?.component, source: '', diff --git a/cli/src/react/__test__/create.test.ts b/cli/src/react/__test__/create.test.ts index b4062f0..c5f91ce 100644 --- a/cli/src/react/__test__/create.test.ts +++ b/cli/src/react/__test__/create.test.ts @@ -22,39 +22,59 @@ describe('createReactCodeConnect', () => { destinationFile: 'test.figma.tsx', config: { parser: 'react' }, mode: 'CREATE', - component: { - id: '1:1', - figmaNodeUrl: 'fake-url', - name: 'Test', - normalizedName: 'Test', - type: 'COMPONENT_SET', - componentPropertyDefinitions: { - BooleanVariant: { - type: 'VARIANT', - defaultValue: 'true', - variantOptions: ['true', 'false'], - }, - BooleanVariant2: { - type: 'VARIANT', - defaultValue: 'True', - variantOptions: ['True', 'False'], - }, - BooleanVariant3: { type: 'VARIANT', defaultValue: 'yes', variantOptions: ['yes', 'no'] }, - BooleanVariant4: { type: 'VARIANT', defaultValue: 'Yes', variantOptions: ['Yes', 'No'] }, - BooleanVariant5: { type: 'VARIANT', defaultValue: 'on', variantOptions: ['on', 'off'] }, - BooleanVariant6: { type: 'VARIANT', defaultValue: 'On', variantOptions: ['On', 'Off'] }, - Variant: { - type: 'VARIANT', - defaultValue: 'Yes', - variantOptions: ['Yes', 'No', 'Intermediate'], - }, - Variant2: { - type: 'VARIANT', - defaultValue: 'Yes', - variantOptions: ['True', 'SomethingElse'], + normalizedName: 'Test', + figmaConnections: [ + { + component: { + id: '1:1', + figmaNodeUrl: 'fake-url', + name: 'Test', + type: 'COMPONENT_SET', + componentPropertyDefinitions: { + BooleanVariant: { + type: 'VARIANT', + defaultValue: 'true', + variantOptions: ['true', 'false'], + }, + BooleanVariant2: { + type: 'VARIANT', + defaultValue: 'True', + variantOptions: ['True', 'False'], + }, + BooleanVariant3: { + type: 'VARIANT', + defaultValue: 'yes', + variantOptions: ['yes', 'no'], + }, + BooleanVariant4: { + type: 'VARIANT', + defaultValue: 'Yes', + variantOptions: ['Yes', 'No'], + }, + BooleanVariant5: { + type: 'VARIANT', + defaultValue: 'on', + variantOptions: ['on', 'off'], + }, + BooleanVariant6: { + type: 'VARIANT', + defaultValue: 'On', + variantOptions: ['On', 'Off'], + }, + Variant: { + type: 'VARIANT', + defaultValue: 'Yes', + variantOptions: ['Yes', 'No', 'Intermediate'], + }, + Variant2: { + type: 'VARIANT', + defaultValue: 'Yes', + variantOptions: ['True', 'SomethingElse'], + }, + }, }, }, - }, + ], }) const expected = await prettier.format( @@ -109,23 +129,27 @@ figma.connect(Test, "fake-url", { destinationFile: 'test.figma.tsx', config: { parser: 'react' }, mode: 'CREATE', - propMapping: {}, - reactTypeSignature: { - someBool: 'false | true', - }, - component: { - id: '1:1', - figmaNodeUrl: 'fake-url', - name: 'Test', - normalizedName: 'Test', - type: 'COMPONENT_SET', - componentPropertyDefinitions: { - Label: { - type: 'TEXT', - defaultValue: 'Some label', + normalizedName: 'Test', + figmaConnections: [ + { + propMapping: {}, + reactTypeSignature: { + someBool: 'false | true', + }, + component: { + id: '1:1', + figmaNodeUrl: 'fake-url', + name: 'Test', + type: 'COMPONENT_SET', + componentPropertyDefinitions: { + Label: { + type: 'TEXT', + defaultValue: 'Some label', + }, + }, }, }, - }, + ], }) const expected = await prettier.format( @@ -168,70 +192,74 @@ figma.connect(Test, "fake-url", { destinationFile: 'test.figma.tsx', config: { parser: 'react' }, mode: 'CREATE', - propMapping: { - actualPropNameForhasIcon: { - kind: IntrinsicKind.Boolean, - args: { - figmaPropName: 'Has icon', - }, - }, - actualPropNameForTitle: { - kind: IntrinsicKind.Boolean, - args: { - figmaPropName: 'Should show title', - valueMapping: { - true: { - kind: IntrinsicKind.String, - args: { - figmaPropName: 'Label', + normalizedName: 'Test', + figmaConnections: [ + { + propMapping: { + actualPropNameForhasIcon: { + kind: IntrinsicKind.Boolean, + args: { + figmaPropName: 'Has icon', + }, + }, + actualPropNameForTitle: { + kind: IntrinsicKind.Boolean, + args: { + figmaPropName: 'Should show title', + valueMapping: { + true: { + kind: IntrinsicKind.String, + args: { + figmaPropName: 'Label', + }, + }, + false: undefined, }, }, - false: undefined, }, }, - }, - }, - reactTypeSignature: { - actualPropNameForhasIcon: 'false | true', - actualPropNameForTitle: '?string', - notMappedProp: 'number', - }, - component: { - id: '1:1', - figmaNodeUrl: 'fake-url', - name: 'Test', - normalizedName: 'Test', - type: 'COMPONENT_SET', - componentPropertyDefinitions: { - BooleanVariant: { - type: 'VARIANT', - defaultValue: 'true', - variantOptions: ['true', 'false'], - }, - 'Should show title': { - type: 'VARIANT', - defaultValue: 'true', - variantOptions: ['true', 'false'], - }, - 'Figma-only variant': { - type: 'VARIANT', - defaultValue: 'this', - variantOptions: ['this', 'that'], + reactTypeSignature: { + actualPropNameForhasIcon: 'false | true', + actualPropNameForTitle: '?string', + notMappedProp: 'number', }, - Label: { - type: 'TEXT', - defaultValue: 'Some label', - }, - 'Some other text': { - type: 'TEXT', - defaultValue: '', - }, - 'Has icon': { - type: 'BOOLEAN', - defaultValue: false, + component: { + id: '1:1', + figmaNodeUrl: 'fake-url', + name: 'Test', + type: 'COMPONENT_SET', + componentPropertyDefinitions: { + BooleanVariant: { + type: 'VARIANT', + defaultValue: 'true', + variantOptions: ['true', 'false'], + }, + 'Should show title': { + type: 'VARIANT', + defaultValue: 'true', + variantOptions: ['true', 'false'], + }, + 'Figma-only variant': { + type: 'VARIANT', + defaultValue: 'this', + variantOptions: ['this', 'that'], + }, + Label: { + type: 'TEXT', + defaultValue: 'Some label', + }, + 'Some other text': { + type: 'TEXT', + defaultValue: '', + }, + 'Has icon': { + type: 'BOOLEAN', + defaultValue: false, + }, + }, }, }, - }, + ], }) const expected = await prettier.format( @@ -286,18 +314,22 @@ figma.connect(Test, "fake-url", { destinationFile: 'test.figma.tsx', config: { parser: 'react' }, mode: 'CREATE', - reactTypeSignature: { - nonOptionalProp: 'false | true', - optionProp: '?string', - }, - component: { - id: '1:1', - figmaNodeUrl: 'fake-url', - name: 'Test', - normalizedName: 'Test', - type: 'COMPONENT_SET', - componentPropertyDefinitions: {}, - }, + normalizedName: 'Test', + figmaConnections: [ + { + reactTypeSignature: { + nonOptionalProp: 'false | true', + optionProp: '?string', + }, + component: { + id: '1:1', + figmaNodeUrl: 'fake-url', + name: 'Test', + type: 'COMPONENT_SET', + componentPropertyDefinitions: {}, + }, + }, + ], }) const expected = await prettier.format( @@ -336,32 +368,36 @@ figma.connect(Test, "fake-url", { destinationFile: 'test.figma.tsx', config: { parser: 'react' }, mode: 'CREATE', - propMapping: { - actualPropNameForhasIcon: { - kind: IntrinsicKind.Boolean, - args: { - figmaPropName: 'Has icon', + normalizedName: 'Test', + figmaConnections: [ + { + propMapping: { + actualPropNameForhasIcon: { + kind: IntrinsicKind.Boolean, + args: { + figmaPropName: 'Has icon', + }, + }, + children: { + kind: IntrinsicKind.Children, + args: { + layers: ['Icon'], + }, + }, }, - }, - children: { - kind: IntrinsicKind.Children, - args: { - layers: ['Icon'], + reactTypeSignature: { + actualPropNameForhasIcon: 'false | true', + children: 'React.ReactNode', + }, + component: { + id: '1:1', + figmaNodeUrl: 'fake-url', + name: 'Test', + type: 'COMPONENT_SET', + componentPropertyDefinitions: {}, }, }, - }, - reactTypeSignature: { - actualPropNameForhasIcon: 'false | true', - children: 'React.ReactNode', - }, - component: { - id: '1:1', - figmaNodeUrl: 'fake-url', - name: 'Test', - normalizedName: 'Test', - type: 'COMPONENT_SET', - componentPropertyDefinitions: {}, - }, + ], }) const expected = await prettier.format( @@ -404,17 +440,21 @@ figma.connect(Test, "fake-url", { await createReactCodeConnect({ destinationDir: path.join('src', 'components', 'figma'), sourceFilepath: path.join('src', 'components', 'buttons', 'PrimaryButton.tsx'), - sourceExport: 'default', config: { parser: 'react' }, mode: 'CREATE', - component: { - id: '1:1', - figmaNodeUrl: 'fake-url', - name: 'Main Button', - normalizedName: 'MainButton', - type: 'COMPONENT_SET', - componentPropertyDefinitions: {}, - }, + normalizedName: 'MainButton', + figmaConnections: [ + { + sourceExport: 'default', + component: { + id: '1:1', + figmaNodeUrl: 'fake-url', + name: 'Main Button', + type: 'COMPONENT_SET', + componentPropertyDefinitions: {}, + }, + }, + ], }) const expected = await prettier.format( diff --git a/cli/src/react/__test__/parser.test.ts b/cli/src/react/__test__/parser.test.ts index 4619be0..377ed5d 100644 --- a/cli/src/react/__test__/parser.test.ts +++ b/cli/src/react/__test__/parser.test.ts @@ -4,6 +4,7 @@ import { CodeConnectReactConfig } from '../../connect/project' import { readFileSync } from 'fs' import { parseCodeConnect, ParserError } from '../../connect/parser_common' import { findAndResolveImports, parseReactDoc, replacePropPlaceholders } from '../parser' +import { getFileInRepositoryRegex } from '../../__test__/utils' async function testParse( file: string, @@ -51,8 +52,9 @@ describe('Parser (JS templates)', () => { expect(result).toMatchObject([ { figmaNode: 'ui/button', - source: - 'https://github.com/figma/code-connect/blob/main/cli/src/react/__test__/components/TestComponents.tsx', + source: expect.stringMatching( + getFileInRepositoryRegex('cli/src/react/__test__/components/TestComponents.tsx'), + ), sourceLocation: { line: 12 }, template: getExpectedTemplate('Button'), templateData: { @@ -68,8 +70,9 @@ describe('Parser (JS templates)', () => { expect(result).toMatchObject([ { figmaNode: 'ui/button', - source: - 'https://github.com/figma/code-connect/blob/main/cli/src/react/__test__/TestTopLevelComponent.tsx', + source: expect.stringMatching( + getFileInRepositoryRegex('cli/src/react/__test__/TestTopLevelComponent.tsx'), + ), sourceLocation: { line: 12 }, template: getExpectedTemplate('Button'), templateData: { @@ -297,8 +300,9 @@ describe('Parser (JS templates)', () => { label: 'React', language: 'typescript', component: 'Button', - source: - 'https://github.com/figma/code-connect/blob/main/cli/src/react/__test__/components/TestComponents.tsx', + source: expect.stringMatching( + getFileInRepositoryRegex('cli/src/react/__test__/components/TestComponents.tsx'), + ), sourceLocation: { line: 12 }, template: getExpectedTemplate(indented ? 'PropMappings_indented' : 'PropMappings'), templateData: { @@ -380,8 +384,9 @@ describe('Parser (JS templates)', () => { label: 'React', language: 'typescript', component: 'Button', - source: - 'https://github.com/figma/code-connect/blob/main/cli/src/react/__test__/components/TestComponents.tsx', + source: expect.stringMatching( + getFileInRepositoryRegex('cli/src/react/__test__/components/TestComponents.tsx'), + ), sourceLocation: { line: 12 }, template: getExpectedTemplate('EnumLikeBooleanFalseProp'), templateData: { @@ -562,8 +567,9 @@ describe('Parser (JS templates)', () => { expect(withAlias).toMatchObject([ { component: 'Button', - source: - 'https://github.com/figma/code-connect/blob/main/cli/src/react/__test__/components/TestComponents.tsx', + source: expect.stringMatching( + getFileInRepositoryRegex('cli/src/react/__test__/components/TestComponents.tsx'), + ), }, ]) }) diff --git a/cli/src/react/create.ts b/cli/src/react/create.ts index 0eb0d55..e72b418 100644 --- a/cli/src/react/create.ts +++ b/cli/src/react/create.ts @@ -5,7 +5,9 @@ import z from 'zod' import { ComponentPropertyDefinition, CreateRequestPayload, + CreateRequestPayloadMulti, CreateResponsePayload, + FigmaConnectionComponent, PropMapping, } from '../connect/parser_executable_types' import path from 'path' @@ -136,10 +138,7 @@ export function getSetOfAllPropsReferencedInPropMapping(obj: Object) { return new Set(mappedProps) } -function generatePropsFromMapping( - component: CreateRequestPayload['component'], - propMapping: PropMapping, -) { +function generatePropsFromMapping(component: FigmaConnectionComponent, propMapping: PropMapping) { const mappedProps: string[] = [] const unmappedProps: string[] = [] @@ -181,7 +180,7 @@ ${ }` } -export function generateProps(component: CreateRequestPayload['component']) { +export function generateProps(component: FigmaConnectionComponent) { const props: string[] = [] if ( !component.componentPropertyDefinitions || @@ -272,11 +271,27 @@ function getImportsPath({ } export async function createReactCodeConnect( - payload: CreateRequestPayload, + payload: CreateRequestPayloadMulti, ): Promise> { - const { component, destinationFile, destinationDir, sourceFilepath, sourceExport, propMapping } = + const { figmaConnections, destinationFile, destinationDir, sourceFilepath, normalizedName } = payload - const { normalizedName, figmaNodeUrl } = component + + const comments = { + MAPPED_PROPS: ` + * \`props\` includes a mapping from your code props to Figma properties. + * You should check this is correct, and update the \`example\` function + * to return the code example you'd like to see in Figma`, + NO_MAPPED_PROPS: ` + * None of your props could be automatically mapped to Figma properties. + * You should update the \`props\` object to include a mapping from your + * code props to Figma properties, and update the \`example\` function to + * return the code example you'd like to see in Figma`, + DEFAULT: ` + * \`props\` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the \`example\` function to return the + * code example you'd like to see in Figma`, + } const sourceFilename = sourceFilepath ? path.parse(sourceFilepath).name.split('.')[0] @@ -295,50 +310,75 @@ export async function createReactCodeConnect( normalizedName, }) - const hasAnyMappedProps = propMapping && Object.keys(propMapping).length > 0 - - const importName = - sourceFilepath && sourceExport - ? sourceExport === 'default' - ? normalizeComponentName(sourceFilename) - : sourceExport - : normalizedName - - let comment = '' - - if (propMapping && hasAnyMappedProps) { - comment = ` - * \`props\` includes a mapping from your code props to Figma properties. - * You should check this is correct, and update the \`example\` function - * to return the code example you'd like to see in Figma` - } else if (propMapping && !hasAnyMappedProps) { - comment = ` - * None of your props could be automatically mapped to Figma properties. - * You should update the \`props\` object to include a mapping from your - * code props to Figma properties, and update the \`example\` function to - * return the code example you'd like to see in Figma` - } else { - comment = ` - * \`props\` includes a mapping from Figma properties and variants to - * suggested values. You should update this to match the props of your - * code component, and update the \`example\` function to return the - * code example you'd like to see in Figma` + let defaultImport = '' + const namedImports: Record = {} + + for (const figmaConnection of figmaConnections) { + const { sourceExport, component } = figmaConnection + + const importName = + sourceFilepath && sourceExport + ? sourceExport === 'default' + ? normalizeComponentName(sourceFilename) + : sourceExport + : normalizedName + + if (sourceExport === 'default') { + defaultImport = importName + } else { + namedImports[component.id] = importName + } } - const codeConnect = ` + if (defaultImport !== '') { + defaultImport = Object.values(namedImports).includes(defaultImport) + ? `${defaultImport}Default` + : defaultImport + } + + let codeConnectCode = '' + for (const figmaConnection of figmaConnections) { + const { propMapping, sourceExport, component, reactTypeSignature } = figmaConnection + + const hasAnyMappedProps = propMapping && Object.keys(propMapping).length > 0 + + const commentType = + propMapping && hasAnyMappedProps + ? 'MAPPED_PROPS' + : propMapping && !hasAnyMappedProps + ? 'NO_MAPPED_PROPS' + : 'DEFAULT' + let comment = comments[commentType] + + let componentName = sourceExport === 'default' ? defaultImport : namedImports[component.id] + + const snippet = `figma.connect(${componentName}, "${component.figmaNodeUrl}", { + props: ${propMapping ? generatePropsFromMapping(component, propMapping) : generateProps(component)}, + example: (props) => ${generateExample(componentName, reactTypeSignature, propMapping)}, + })` + + codeConnectCode += ` + + /** + * -- This file was auto-generated by Code Connect --${comment} + */ + + ${snippet} + ` + } + + const comma = Object.keys(namedImports).length > 0 && defaultImport ? ',' : '' + const namedImportList = + Object.values(namedImports).length > 0 ? `{${Object.values(namedImports).join(',')}}` : '' + + let codeConnect = ` import React from 'react' -import ${sourceExport === 'default' ? importName : `{ ${importName} }`} from '${importsPath}' +import ${defaultImport} ${comma} ${namedImportList} from '${importsPath}' import figma from '@figma/code-connect' -/** - * -- This file was auto-generated by Code Connect --${comment} - */ - -figma.connect(${importName}, "${figmaNodeUrl}", { - props: ${propMapping ? generatePropsFromMapping(component, propMapping) : generateProps(component)}, - example: (props) => ${generateExample(importName, payload.reactTypeSignature, propMapping)}, -}) +${codeConnectCode} ` + let formatted = prettier.format(codeConnect, { parser: 'typescript', semi: false, diff --git a/cli/src/react/parser.ts b/cli/src/react/parser.ts index 792f92e..8de46a5 100644 --- a/cli/src/react/parser.ts +++ b/cli/src/react/parser.ts @@ -1,5 +1,10 @@ import ts from 'typescript' -import { getRemoteFileUrl, mapImportPath } from '../connect/project' +import { + CodeConnectParser, + DEFAULT_LABEL_PER_PARSER, + getRemoteFileUrl, + mapImportPath, +} from '../connect/project' import { logger } from '../common/logging' import { bfsFindNode, @@ -962,7 +967,7 @@ export async function parseReactDoc( return { figmaNode, - label: 'React', + label: DEFAULT_LABEL_PER_PARSER.react!, language: 'typescript', component: metadata?.component, source: metadata?.source ? getRemoteFileUrl(metadata.source, repoUrl) : '', diff --git a/cli/src/react/parser_template_helpers.ts b/cli/src/react/parser_template_helpers.ts index 9d95f6c..76322c2 100644 --- a/cli/src/react/parser_template_helpers.ts +++ b/cli/src/react/parser_template_helpers.ts @@ -35,8 +35,16 @@ export type FCCValue = | typeof _fcc_object | typeof _fcc_templateString | typeof _fcc_reactComponent + | typeof _fcc_array > +export function _fcc_array($value: any[]) { + return { + $value, + $type: 'array', + } as const +} + export function _fcc_jsxElement($value: string) { return { $value, @@ -80,10 +88,21 @@ export function _fcc_reactComponent($value: string) { } as const } +function isReactComponentArray( + prop: FCCValue | (CodeSection | InstanceSection | ErrorSection)[], +): prop is (InstanceSection | CodeSection | ErrorSection)[] { + return ( + Array.isArray(prop) && + prop.every((item) => item.type === 'INSTANCE' || item.type === 'CODE' || item.type === 'ERROR') + ) +} + // Render a prop value passed to an object literal based on its type. // for example: