├── .gitignore ├── assets ├── 004-name.png ├── 008-section.png ├── 005-custom-avatar.png ├── 006-custom-colors.png ├── 007-custom-fonts.png ├── 003-avatar-name-github.png ├── 001-avatar-name-alias-meta-github.png └── 002-avatar-name-alias-meta-twitter.png ├── src ├── yml.d.ts ├── types.ts ├── defaultChartData.yml ├── ui.tsx └── main.ts ├── manifest.json ├── tsconfig.json ├── examples ├── 004-name.yml ├── 003-avatar-name-github.yml ├── 001-avatar-name-alias-meta-github.yml ├── 004-name.json ├── 008-section.yml ├── 002-avatar-name-alias-meta-twitter.yml ├── 006-custom-colors.yml ├── 003-avatar-name-github.json ├── 007-custom-fonts.yml ├── 001-avatar-name-alias-meta-github.json ├── 008-section.json ├── 002-avatar-name-alias-meta-twitter.json ├── 006-custom-colors.json ├── 007-custom-fonts.json ├── 005-custom-avatar.yml └── 005-custom-avatar.json ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | *.css.d.ts 4 | build/ 5 | node_modules/ 6 | 7 | -------------------------------------------------------------------------------- /assets/004-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/figma-orgchart/HEAD/assets/004-name.png -------------------------------------------------------------------------------- /src/yml.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.yml' { 2 | const data: string 3 | export default data 4 | } 5 | -------------------------------------------------------------------------------- /assets/008-section.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/figma-orgchart/HEAD/assets/008-section.png -------------------------------------------------------------------------------- /assets/005-custom-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/figma-orgchart/HEAD/assets/005-custom-avatar.png -------------------------------------------------------------------------------- /assets/006-custom-colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/figma-orgchart/HEAD/assets/006-custom-colors.png -------------------------------------------------------------------------------- /assets/007-custom-fonts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/figma-orgchart/HEAD/assets/007-custom-fonts.png -------------------------------------------------------------------------------- /assets/003-avatar-name-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/figma-orgchart/HEAD/assets/003-avatar-name-github.png -------------------------------------------------------------------------------- /assets/001-avatar-name-alias-meta-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/figma-orgchart/HEAD/assets/001-avatar-name-alias-meta-github.png -------------------------------------------------------------------------------- /assets/002-avatar-name-alias-meta-twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamuso/figma-orgchart/HEAD/assets/002-avatar-name-alias-meta-twitter.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "api": "1.0.0", 3 | "editorType": ["figma"], 4 | "id": "1091247524080548244", 5 | "name": "Orgchart", 6 | "main": "build/main.js", 7 | "ui": "build/ui.js" 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@create-figma-plugin/tsconfig", 3 | "compilerOptions": { 4 | "typeRoots": ["node_modules/@figma", "node_modules/@types"], 5 | "resolveJsonModule": true, 6 | "esModuleInterop": true, 7 | "useUnknownInCatchVariables": false 8 | }, 9 | "include": ["src/**/*.ts", "src/**/*.tsx"] 10 | } 11 | -------------------------------------------------------------------------------- /examples/004-name.yml: -------------------------------------------------------------------------------- 1 | team: Main team 2 | config: 3 | alias: false 4 | meta: false 5 | avatar: false 6 | manager: 7 | name: Jose Florido 8 | teams: 9 | - team: Team A 10 | manager: 11 | name: Inma Rodenas 12 | members: 13 | - name: Andrea Espinosa 14 | - name: Adrián Mato 15 | 16 | - team: Team B 17 | manager: 18 | name: Asier Martínez 19 | members: 20 | - name: Laura Martínez 21 | - name: Open Role 22 | -------------------------------------------------------------------------------- /examples/003-avatar-name-github.yml: -------------------------------------------------------------------------------- 1 | team: Main team 2 | config: 3 | alias: false 4 | meta: false 5 | manager: 6 | name: Jose Florido 7 | alias: joseflorido 8 | teams: 9 | - team: Team A 10 | manager: 11 | name: Inma Rodenas 12 | alias: inmarodenas 13 | members: 14 | - name: Andrea Espinosa 15 | alias: mamusino 16 | - name: Adrián Mato 17 | alias: adrianmg 18 | 19 | - team: Team B 20 | manager: 21 | name: Asier Martínez 22 | alias: asiermartinez 23 | members: 24 | - name: Laura Martínez 25 | alias: killermuffin 26 | - name: Open Role 27 | alias: '' 28 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { EventHandler } from '@create-figma-plugin/utilities' 2 | 3 | export interface CreateChartHandler extends EventHandler { 4 | name: 'CREATE_CHART' 5 | handler: (chartData: string) => void 6 | } 7 | 8 | export interface CloseHandler extends EventHandler { 9 | name: 'CLOSE' 10 | handler: () => void 11 | } 12 | 13 | export type ChartConfig = { 14 | avatar: boolean 15 | name: boolean 16 | alias: boolean 17 | meta: boolean 18 | ogurl: string 19 | color: { 20 | [key: string]: string 21 | } 22 | text: { 23 | [key: string]: ChartConfigText 24 | } 25 | } 26 | 27 | export type ChartConfigText = { 28 | family: string 29 | style: string 30 | size: number 31 | } 32 | -------------------------------------------------------------------------------- /examples/001-avatar-name-alias-meta-github.yml: -------------------------------------------------------------------------------- 1 | team: Main team 2 | manager: 3 | name: Jose Florido 4 | alias: joseflorido 5 | meta: Palo Alto, CA 6 | teams: 7 | - team: Team A 8 | manager: 9 | name: Inma Rodenas 10 | alias: inmarodenas 11 | meta: Madrid, Spain 12 | members: 13 | - name: Andrea Espinosa 14 | alias: mamusino 15 | meta: Metadata 16 | - name: Adrián Mato 17 | alias: adrianmg 18 | meta: San Bruno, CA 19 | 20 | - team: Team B 21 | manager: 22 | name: Asier Martínez 23 | alias: asiermartinez 24 | meta: Copenhague, Denmark 25 | members: 26 | - name: Laura Martínez 27 | alias: killermuffin 28 | meta: Madrid, Spain 29 | - name: Open Role 30 | alias: '' 31 | meta: '' 32 | -------------------------------------------------------------------------------- /examples/004-name.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "alias": false, 4 | "meta": false, 5 | "avatar": false 6 | }, 7 | "team": "Main team", 8 | "manager": { 9 | "name": "Jose Florido" 10 | }, 11 | "teams": [ 12 | { 13 | "team": "Team A", 14 | "manager": { 15 | "name": "Inma Rodenas" 16 | }, 17 | "members": [ 18 | { 19 | "name": "Andrea Espinosa" 20 | }, 21 | { 22 | "name": "Adrián Mato" 23 | } 24 | ] 25 | }, 26 | { 27 | "team": "Team B", 28 | "manager": { 29 | "name": "Asier Martínez" 30 | }, 31 | "members": [ 32 | { 33 | "name": "Laura Martínez" 34 | }, 35 | { 36 | "name": "Open Role" 37 | } 38 | ] 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "figma-plugin": { 3 | "editorType": [ 4 | "figma" 5 | ], 6 | "id": "1091247524080548244", 7 | "name": "Orgchart", 8 | "main": "src/main.ts", 9 | "ui": "src/ui.tsx" 10 | }, 11 | "scripts": { 12 | "build": "build-figma-plugin --typecheck --minify", 13 | "watch": "build-figma-plugin --typecheck --watch" 14 | }, 15 | "dependencies": { 16 | "@create-figma-plugin/ui": "^1.8.4", 17 | "@create-figma-plugin/utilities": "^1.8.4", 18 | "@types/js-yaml": "^4.0.5", 19 | "js-yaml": "^4.1.0", 20 | "preact": "^10.6.6" 21 | }, 22 | "devDependencies": { 23 | "@create-figma-plugin/build": "^1.8.4", 24 | "@create-figma-plugin/tsconfig": "^1.8.4", 25 | "@figma/plugin-typings": "^1.42.1", 26 | "@types/node": "^17.0.23", 27 | "typescript": "^4.6.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/008-section.yml: -------------------------------------------------------------------------------- 1 | team: Main team 2 | manager: 3 | name: Jose Florido 4 | alias: joseflorido 5 | meta: Palo Alto, CA 6 | teams: 7 | - team: Team A 8 | manager: 9 | name: Inma Rodenas 10 | alias: inmarodenas 11 | meta: Madrid, Spain 12 | members: 13 | - name: Andrea Espinosa 14 | alias: mamusino 15 | meta: Metadata 16 | - section: section 17 | - name: Adrián Mato 18 | alias: adrianmg 19 | meta: San Bruno, CA 20 | 21 | - team: Team B 22 | manager: 23 | name: Asier Martínez 24 | alias: asiermartinez 25 | meta: Copenhague, Denmark 26 | members: 27 | - name: Laura Martínez 28 | alias: killermuffin 29 | meta: Madrid, Spain 30 | - section: section 31 | - name: Open Role 32 | alias: '' 33 | meta: '' 34 | -------------------------------------------------------------------------------- /examples/002-avatar-name-alias-meta-twitter.yml: -------------------------------------------------------------------------------- 1 | team: Main team 2 | config: 3 | ogurl: https://twitter.com/ 4 | manager: 5 | name: Jose Florido 6 | alias: joseflorido 7 | meta: Palo Alto, CA 8 | teams: 9 | - team: Team A 10 | manager: 11 | name: Inma Rodenas 12 | alias: inmarodenas 13 | meta: Madrid, Spain 14 | members: 15 | - name: Andrea Espinosa 16 | alias: mamusino 17 | meta: Metadata 18 | - name: Adrián Mato 19 | alias: adrianmg 20 | meta: San Bruno, CA 21 | 22 | - team: Team B 23 | manager: 24 | name: Asier Martínez 25 | alias: asiermartinez 26 | meta: Copenhague, Denmark 27 | members: 28 | - name: Laura Martínez 29 | alias: killermuffin 30 | meta: Madrid, Spain 31 | - name: Open Role 32 | alias: '' 33 | meta: '' 34 | -------------------------------------------------------------------------------- /examples/006-custom-colors.yml: -------------------------------------------------------------------------------- 1 | team: Main team 2 | config: 3 | color: 4 | border: 26343A 5 | background: 26343A 6 | primarytext: FFFFFF 7 | secondarytext: ABB4B9 8 | manager: 9 | name: Jose Florido 10 | alias: joseflorido 11 | meta: Palo Alto, CA 12 | teams: 13 | - team: Team A 14 | manager: 15 | name: Inma Rodenas 16 | alias: inmarodenas 17 | meta: Madrid, Spain 18 | members: 19 | - name: Andrea Espinosa 20 | alias: mamusino 21 | meta: Metadata 22 | - name: Adrián Mato 23 | alias: adrianmg 24 | meta: San Bruno, CA 25 | 26 | - team: Team B 27 | manager: 28 | name: Asier Martínez 29 | alias: asiermartinez 30 | meta: Copenhague, Denmark 31 | members: 32 | - name: Laura Martínez 33 | alias: killermuffin 34 | meta: Madrid, Spain 35 | - name: Open Role 36 | alias: '' 37 | meta: '' 38 | -------------------------------------------------------------------------------- /examples/003-avatar-name-github.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "alias": false, 4 | "meta": false 5 | }, 6 | "team": "Main team", 7 | "manager": { 8 | "name": "Jose Florido", 9 | "alias": "joseflorido" 10 | }, 11 | "teams": [ 12 | { 13 | "team": "Team A", 14 | "manager": { 15 | "name": "Inma Rodenas", 16 | "alias": "inmarodenas" 17 | }, 18 | "members": [ 19 | { 20 | "name": "Andrea Espinosa", 21 | "alias": "mamuso" 22 | }, 23 | { 24 | "name": "Adrián Mato", 25 | "alias": "adrianmg" 26 | } 27 | ] 28 | }, 29 | { 30 | "team": "Team B", 31 | "manager": { 32 | "name": "Asier Martínez", 33 | "alias": "asiermartinez" 34 | }, 35 | "members": [ 36 | { 37 | "name": "Laura Martínez", 38 | "alias": "killermuffin" 39 | }, 40 | { 41 | "name": "Open Role", 42 | "alias": "" 43 | } 44 | ] 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /examples/007-custom-fonts.yml: -------------------------------------------------------------------------------- 1 | team: Main team 2 | config: 3 | text: 4 | label: 5 | family: Comic Neue 6 | style: Bold 7 | size: 32 8 | name: 9 | family: Comic Neue 10 | style: Bold 11 | size: 20 12 | alias: 13 | family: Times 14 | style: Regular 15 | size: 12 16 | meta: 17 | family: Times 18 | style: Regular 19 | size: 12 20 | manager: 21 | name: Jose Florido 22 | alias: joseflorido 23 | meta: Palo Alto, CA 24 | teams: 25 | - team: Team A 26 | manager: 27 | name: Inma Rodenas 28 | alias: inmarodenas 29 | meta: Madrid, Spain 30 | members: 31 | - name: Andrea Espinosa 32 | alias: mamusino 33 | meta: Metadata 34 | - name: Adrián Mato 35 | alias: adrianmg 36 | meta: San Bruno, CA 37 | 38 | - team: Team B 39 | manager: 40 | name: Asier Martínez 41 | alias: asiermartinez 42 | meta: Copenhague, Denmark 43 | members: 44 | - name: Laura Martínez 45 | alias: killermuffin 46 | meta: Madrid, Spain 47 | - name: Open Role 48 | alias: '' 49 | meta: '' 50 | -------------------------------------------------------------------------------- /examples/001-avatar-name-alias-meta-github.json: -------------------------------------------------------------------------------- 1 | { 2 | "team": "Main team", 3 | "manager": { 4 | "name": "Jose Florido", 5 | "alias": "joseflorido", 6 | "meta": "Palo Alto, CA" 7 | }, 8 | "teams": [ 9 | { 10 | "team": "Team A", 11 | "manager": { 12 | "name": "Inma Rodenas", 13 | "alias": "inmarodenas", 14 | "meta": "Prague, Czech Republic" 15 | }, 16 | "members": [ 17 | { 18 | "name": "Andrea Espinosa", 19 | "alias": "mamusino", 20 | "meta": "Madrid, Spain" 21 | }, 22 | { 23 | "name": "Adrián Mato", 24 | "alias": "adrianmg", 25 | "meta": "San Bruno, CA" 26 | } 27 | ] 28 | }, 29 | { 30 | "team": "Team B", 31 | "manager": { 32 | "name": "Asier Martínez", 33 | "alias": "asiermartinez", 34 | "meta": "Copenhague, Denmark" 35 | }, 36 | "members": [ 37 | { 38 | "name": "Laura Martínez", 39 | "alias": "killermuffin", 40 | "meta": "Madrid, Spain" 41 | }, 42 | { 43 | "name": "Open Role", 44 | "alias": "", 45 | "meta": "" 46 | } 47 | ] 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /examples/008-section.json: -------------------------------------------------------------------------------- 1 | { 2 | "team": "Main team", 3 | "manager": { 4 | "name": "Jose Florido", 5 | "alias": "joseflorido", 6 | "meta": "Palo Alto, CA" 7 | }, 8 | "teams": [ 9 | { 10 | "team": "Team A", 11 | "manager": { 12 | "name": "Inma Rodenas", 13 | "alias": "inmarodenas", 14 | "meta": "Prague, Czech Republic" 15 | }, 16 | "members": [ 17 | { 18 | "name": "Andrea Espinosa", 19 | "alias": "mamusino", 20 | "meta": "Madrid, Spain" 21 | }, 22 | { "section": "Section" }, 23 | { 24 | "name": "Adrián Mato", 25 | "alias": "adrianmg", 26 | "meta": "San Bruno, CA" 27 | } 28 | ] 29 | }, 30 | { 31 | "team": "Team B", 32 | "manager": { 33 | "name": "Asier Martínez", 34 | "alias": "asiermartinez", 35 | "meta": "Copenhague, Denmark" 36 | }, 37 | "members": [ 38 | { 39 | "name": "Laura Martínez", 40 | "alias": "killermuffin", 41 | "meta": "Madrid, Spain" 42 | }, 43 | { "section": "Section" }, 44 | { 45 | "name": "Open Role", 46 | "alias": "", 47 | "meta": "" 48 | } 49 | ] 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /examples/002-avatar-name-alias-meta-twitter.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "ogurl": "https://twitter.com/" 4 | }, 5 | "team": "Main team", 6 | "manager": { 7 | "name": "Jose Florido", 8 | "alias": "joseflorido", 9 | "meta": "Palo Alto, CA" 10 | }, 11 | "teams": [ 12 | { 13 | "team": "Team A", 14 | "manager": { 15 | "name": "Inma Rodenas", 16 | "alias": "inmarodenas", 17 | "meta": "Prague, Czech Republic" 18 | }, 19 | "members": [ 20 | { 21 | "name": "Andrea Espinosa", 22 | "alias": "mamusino", 23 | "meta": "Madrid, Spain" 24 | }, 25 | { 26 | "name": "Adrián Mato", 27 | "alias": "adrianmg", 28 | "meta": "San Bruno, CA" 29 | } 30 | ] 31 | }, 32 | { 33 | "team": "Team B", 34 | "manager": { 35 | "name": "Asier Martínez", 36 | "alias": "asiermartinez", 37 | "meta": "Copenhague, Denmark" 38 | }, 39 | "members": [ 40 | { 41 | "name": "Laura Martínez", 42 | "alias": "killermuffin", 43 | "meta": "Madrid, Spain" 44 | }, 45 | { 46 | "name": "Open Role", 47 | "alias": "", 48 | "meta": "" 49 | } 50 | ] 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /examples/006-custom-colors.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "color": { 4 | "border": "26343A", 5 | "background": "26343A", 6 | "primarytext": "FFFFFF", 7 | "secondarytext": "ABB4B9" 8 | } 9 | }, 10 | "team": "Main team", 11 | "manager": { 12 | "name": "Jose Florido", 13 | "alias": "joseflorido", 14 | "meta": "Palo Alto, CA" 15 | }, 16 | "teams": [ 17 | { 18 | "team": "Team A", 19 | "manager": { 20 | "name": "Inma Rodenas", 21 | "alias": "inmarodenas", 22 | "meta": "Prague, Czech Republic" 23 | }, 24 | "members": [ 25 | { 26 | "name": "Andrea Espinosa", 27 | "alias": "mamusino", 28 | "meta": "Madrid, Spain" 29 | }, 30 | { 31 | "name": "Adrián Mato", 32 | "alias": "adrianmg", 33 | "meta": "San Bruno, CA" 34 | } 35 | ] 36 | }, 37 | { 38 | "team": "Team B", 39 | "manager": { 40 | "name": "Asier Martínez", 41 | "alias": "asiermartinez", 42 | "meta": "Copenhague, Denmark" 43 | }, 44 | "members": [ 45 | { 46 | "name": "Laura Martínez", 47 | "alias": "killermuffin", 48 | "meta": "Madrid, Spain" 49 | }, 50 | { 51 | "name": "Open Role", 52 | "alias": "", 53 | "meta": "" 54 | } 55 | ] 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /examples/007-custom-fonts.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "text": { 4 | "label": { "family": "Comic Neue", "style": "Bold", "size": 32 }, 5 | "name": { "family": "Comic Neue", "style": "Bold", "size": 20 }, 6 | "alias": { "family": "Times", "style": "Regular", "size": 12 }, 7 | "meta": { "family": "Times", "style": "Regular", "size": 12 } 8 | } 9 | }, 10 | "team": "Main team", 11 | "manager": { 12 | "name": "Jose Florido", 13 | "alias": "joseflorido", 14 | "meta": "Palo Alto, CA" 15 | }, 16 | "teams": [ 17 | { 18 | "team": "Team A", 19 | "manager": { 20 | "name": "Inma Rodenas", 21 | "alias": "inmarodenas", 22 | "meta": "Prague, Czech Republic" 23 | }, 24 | "members": [ 25 | { 26 | "name": "Andrea Espinosa", 27 | "alias": "mamusino", 28 | "meta": "Madrid, Spain" 29 | }, 30 | { 31 | "name": "Adrián Mato", 32 | "alias": "adrianmg", 33 | "meta": "San Bruno, CA" 34 | } 35 | ] 36 | }, 37 | { 38 | "team": "Team B", 39 | "manager": { 40 | "name": "Asier Martínez", 41 | "alias": "asiermartinez", 42 | "meta": "Copenhague, Denmark" 43 | }, 44 | "members": [ 45 | { 46 | "name": "Laura Martínez", 47 | "alias": "killermuffin", 48 | "meta": "Madrid, Spain" 49 | }, 50 | { 51 | "name": "Open Role", 52 | "alias": "", 53 | "meta": "" 54 | } 55 | ] 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /examples/005-custom-avatar.yml: -------------------------------------------------------------------------------- 1 | team: Main team 2 | manager: 3 | name: Jose Florido 4 | alias: joseflorido 5 | meta: Palo Alto, CA 6 | avatar: https://static.wikia.nocookie.net/theoffice/images/e/e0/Character_-_RyanHoward.PNG/revision/latest/top-crop/width/200/height/150?cb=20200414174545 7 | teams: 8 | - team: Team A 9 | manager: 10 | name: Inma Rodenas 11 | alias: inmarodenas 12 | meta: Madrid, Spain 13 | avatar: https://static.wikia.nocookie.net/theoffice/images/b/be/Character_-_MichaelScott.PNG/revision/latest/top-crop/width/200/height/150?cb=20200413224550 14 | members: 15 | - name: Andrea Espinosa 16 | alias: mamusino 17 | meta: Metadata 18 | avatar: https://static.wikia.nocookie.net/theoffice/images/c/c5/Dwight_.jpg/revision/latest/top-crop/width/200/height/150?cb=20200413224550 19 | - name: Adrián Mato 20 | alias: adrianmg 21 | meta: San Bruno, CA 22 | avatar: https://static.wikia.nocookie.net/theoffice/images/e/e9/Character_-_JimHalpert.PNG/revision/latest/top-crop/width/200/height/150?cb=20200413224550 23 | 24 | - team: Team B 25 | manager: 26 | name: Asier Martínez 27 | alias: asiermartinez 28 | meta: Copenhague, Denmark 29 | avatar: https://static.wikia.nocookie.net/theoffice/images/2/20/2009Creed.jpg/revision/latest/top-crop/width/200/height/150?cb=20200413224550 30 | members: 31 | - name: Laura Martínez 32 | alias: killermuffin 33 | meta: Madrid, Spain 34 | avatar: https://static.wikia.nocookie.net/theoffice/images/0/0b/Angela_Martin.jpg/revision/latest/top-crop/width/200/height/150?cb=20200413224550 35 | - name: Open Role 36 | alias: '' 37 | meta: '' 38 | -------------------------------------------------------------------------------- /examples/005-custom-avatar.json: -------------------------------------------------------------------------------- 1 | { 2 | "team": "Main team", 3 | "manager": { 4 | "name": "Jose Florido", 5 | "alias": "joseflorido", 6 | "meta": "Palo Alto, CA", 7 | "avatar": "https://static.wikia.nocookie.net/theoffice/images/e/e0/Character_-_RyanHoward.PNG/revision/latest/top-crop/width/200/height/150?cb=20200414174545" 8 | }, 9 | "teams": [ 10 | { 11 | "team": "Team A", 12 | "manager": { 13 | "name": "Inma Rodenas", 14 | "alias": "inmarodenas", 15 | "meta": "Prague, Czech Republic", 16 | "avatar": "https://static.wikia.nocookie.net/theoffice/images/b/be/Character_-_MichaelScott.PNG/revision/latest/top-crop/width/200/height/150?cb=20200413224550" 17 | }, 18 | "members": [ 19 | { 20 | "name": "Andrea Espinosa", 21 | "alias": "mamusino", 22 | "meta": "Madrid, Spain", 23 | "avatar": "https://static.wikia.nocookie.net/theoffice/images/c/c5/Dwight_.jpg/revision/latest/top-crop/width/200/height/150?cb=20200413224550" 24 | }, 25 | { 26 | "name": "Adrián Mato", 27 | "alias": "adrianmg", 28 | "meta": "San Bruno, CA", 29 | "avatar": "https://static.wikia.nocookie.net/theoffice/images/e/e9/Character_-_JimHalpert.PNG/revision/latest/top-crop/width/200/height/150?cb=20200413224550" 30 | } 31 | ] 32 | }, 33 | { 34 | "team": "Team B", 35 | "manager": { 36 | "name": "Asier Martínez", 37 | "alias": "asiermartinez", 38 | "meta": "Copenhague, Denmark", 39 | "avatar": "https://static.wikia.nocookie.net/theoffice/images/2/20/2009Creed.jpg/revision/latest/top-crop/width/200/height/150?cb=20200413224550" 40 | }, 41 | "members": [ 42 | { 43 | "name": "Laura Martínez", 44 | "alias": "killermuffin", 45 | "meta": "Madrid, Spain", 46 | "avatar": "https://static.wikia.nocookie.net/theoffice/images/0/0b/Angela_Martin.jpg/revision/latest/top-crop/width/200/height/150?cb=20200413224550" 47 | }, 48 | { 49 | "name": "Open Role", 50 | "alias": "", 51 | "meta": "" 52 | } 53 | ] 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /src/defaultChartData.yml: -------------------------------------------------------------------------------- 1 | team: Main team 2 | manager: 3 | name: Team manager 4 | alias: '' 5 | meta: Metadata 6 | teams: 7 | - team: Team A 8 | manager: 9 | name: Team manager 10 | alias: '' 11 | meta: Metadata 12 | members: 13 | - name: Team member 1 14 | alias: '' 15 | meta: Metadata 16 | - name: Team member 2 17 | alias: '' 18 | meta: Metadata 19 | - name: Team member 3 20 | alias: '' 21 | meta: Metadata 22 | 23 | - team: Team B 24 | manager: 25 | name: Team manager 26 | alias: '' 27 | meta: Metadata 28 | members: 29 | - name: Team member 1 30 | alias: '' 31 | meta: Metadata 32 | - name: Team member 2 33 | alias: '' 34 | meta: Metadata 35 | 36 | - team: Team C 37 | manager: 38 | name: Team manager 39 | alias: '' 40 | meta: Metadata 41 | members: 42 | - name: Team member 1 43 | alias: '' 44 | meta: Metadata 45 | - name: Team member 2 46 | alias: '' 47 | meta: Metadata 48 | - name: Team member 3 49 | alias: '' 50 | meta: Metadata 51 | teams: 52 | - team: Subteam A 53 | manager: 54 | name: Team manager 55 | alias: '' 56 | meta: Metadata 57 | members: 58 | - name: Team member 1 59 | alias: '' 60 | meta: Metadata 61 | - name: Team member 2 62 | alias: '' 63 | meta: Metadata 64 | - name: Team member 3 65 | alias: '' 66 | meta: Metadata 67 | 68 | - team: Subteam B 69 | manager: 70 | name: Team manager 71 | alias: '' 72 | meta: Metadata 73 | members: 74 | - name: Team member 1 75 | alias: '' 76 | meta: Metadata 77 | 78 | - team: Subteam C 79 | manager: 80 | name: Team manager 81 | alias: '' 82 | meta: Metadata 83 | members: 84 | - name: Team member 1 85 | alias: '' 86 | meta: Metadata 87 | - name: Team member 2 88 | alias: '' 89 | meta: Metadata 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Orgchart – a figma plugin 2 | 3 | Create an org chart in Figma from a JSON or a YAML file. Try it [here](https://www.figma.com/community/plugin/1091247524080548244/Orgchart). 4 | 5 | [![Figma Orgchart](https://user-images.githubusercontent.com/3992/161352441-ad5ed9cf-8fe2-45fb-860c-cce430de40c1.png)](https://www.figma.com/community/plugin/1091247524080548244/Orgchart) 6 | 7 | ## Run the plugin locally 8 | 9 | - Clone the repository: `git clone https://github.com/mamuso/figma-orgchart.git` 10 | - Go to the directory: `cd figma-orgchart` 11 | - Install dependencies: `npm i` 12 | - Build the plugin: `npm run watch` 13 | - In the Figma desktop app, open a Figma document, and go to `Plugins > Development > Import plugin from manifest` 14 | - Select the `figma-orgchart/manifest.json` file 15 | 16 | If you run the plugin locally, you can edit `src/defautChartData.json` to change the default chart data. 17 | 18 | ## JSON/YAML data 19 | 20 | You need to provide a JSON or a YAML with your team structure. You can use the following keys: 21 | 22 | - `team`: name of the team 23 | - `manager`: will be represented at the top of the team 24 | - `members`: array of members of a team 25 | - `teams`: array of teams 26 | - `section`: it will create a label with the text that you provide 27 | 28 | Each manager and member can have the following keys: 29 | 30 | - `name`: name of the person 31 | - `alias`: title alias of the person. By default, it will expect a GitHub alias and will try to fill the avatar 32 | - `meta`: additional information that you'll like to provide 33 | - `avatar`: you can override the avatar providing a URL of an image 34 | 35 | ## Aditional configurations 36 | 37 | By default, the plugin assumes the following configuration: 38 | 39 | ```json 40 | { 41 | "avatar": true, 42 | "name": true, 43 | "alias": true, 44 | "meta": true, 45 | "ogurl": "https://github.com/", 46 | "color": { 47 | "border": "EEECF3", 48 | "background": "FFFFFF", 49 | "primarytext": "444D56", 50 | "secondarytext": "A1A6AA" 51 | }, 52 | "text": { 53 | "label": { "family": "Helvetica Neue", "style": "Bold", "size": 20 }, 54 | "section": { "family": "Helvetica Neue", "style": "Bold", "size": 18 }, 55 | "name": { "family": "Helvetica Neue", "style": "Bold", "size": 16 }, 56 | "alias": { "family": "Helvetica Neue", "style": "Regular", "size": 12 }, 57 | "meta": { "family": "Helvetica Neue", "style": "Regular", "size": 12 } 58 | } 59 | } 60 | ``` 61 | 62 | You can change the default configuration by adding a `config` key to the root of the JSON/YAML file. Check some of the examples below to learn how to change the design of the chart. 63 | 64 | ## Examples 65 | 66 | ### An org chart with Avatar, Name, Alias, and Meta 67 | 68 | - [001-avatar-name-alias-meta-github.json](examples/001-avatar-name-alias-meta-github.json) 69 | - [001-avatar-name-alias-meta-github.yml](examples/001-avatar-name-alias-meta-github.yml) 70 | 71 | ![](assets/001-avatar-name-alias-meta-github.png) 72 | 73 | ### Loading avatars from Twitter 74 | 75 | - [002-avatar-name-alias-meta-twitter.json](examples/002-avatar-name-alias-meta-twitter.json) 76 | - [002-avatar-name-alias-meta-twitter.yml](examples/002-avatar-name-alias-meta-twitter.yml) 77 | 78 | ![](assets/002-avatar-name-alias-meta-twitter.png) 79 | 80 | ### Hidding Alias and Meta field 81 | 82 | - [003-avatar-name-github.json](examples/003-avatar-name-github.json) 83 | - [003-avatar-name-github.yml](examples/003-avatar-name-github.yml) 84 | 85 | ![](assets/003-avatar-name-github.png) 86 | 87 | ### Only shows the name 88 | 89 | - [004-name.json](examples/004-name.json) 90 | - [004-name.yml](examples/004-name.yml) 91 | 92 | ![](assets/004-name.png) 93 | 94 | ### Loading avatars from URLs 95 | 96 | - [005-custom-avatar.json](examples/005-custom-avatar.json) 97 | - [005-custom-avatar.yml](examples/005-custom-avatar.yml) 98 | 99 | ![](assets/005-custom-avatar.png) 100 | 101 | ### Using custom colors 102 | 103 | - [006-custom-colors.json](examples/006-custom-colors.json) 104 | - [006-custom-colors.yml](examples/006-custom-colors.yml) 105 | 106 | ![](assets/006-custom-colors.png) 107 | 108 | ### Using custom fonts 109 | 110 | - [007-custom-fonts.json](examples/007-custom-fonts.json) 111 | - [007-custom-fonts.yml](examples/007-custom-fonts.yml) 112 | 113 | ![](assets/007-custom-fonts.png) 114 | 115 | ### Sections 116 | 117 | - [008-section.json](examples/008-section.json) 118 | - [008-section.yml](examples/008-section.yml) 119 | 120 | ![](assets/008-section.png) 121 | -------------------------------------------------------------------------------- /src/ui.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Container, Inline, render, Text, TextboxMultiline, VerticalSpace } from '@create-figma-plugin/ui' 2 | import { emit } from '@create-figma-plugin/utilities' 3 | import { h } from 'preact' 4 | import { useCallback, useState } from 'preact/hooks' 5 | import { CloseHandler, CreateChartHandler } from './types' 6 | import defaultChartData from "./defaultChartData.yml" 7 | 8 | function Plugin() { 9 | const [chartData, setChartData] = useState('') 10 | const [isLoading, setIsLoading] = useState(false) 11 | 12 | /* ------------------------------------------------------------------------- 13 | Loading example data from defaultChartData.json 14 | ------------------------------------------------------------------------- */ 15 | // if (chartData == '') setChartData(JSON.stringify(defaultChartData, null, "\t")) 16 | if (chartData == '') setChartData(defaultChartData) 17 | 18 | /* ------------------------------------------------------------------------- 19 | Submit data chart and kick off the drawing process 20 | ------------------------------------------------------------------------- */ 21 | const handleCreateChartButtonClick = useCallback( 22 | function () { 23 | if (chartData !== null) { 24 | setIsLoading(true) 25 | emit('CREATE_CHART', chartData) 26 | } 27 | }, [chartData] 28 | ) 29 | 30 | /* ------------------------------------------------------------------------- 31 | Cancel and close 32 | ------------------------------------------------------------------------- */ 33 | const handleCloseButtonClick = useCallback( 34 | function () { 35 | emit('CLOSE') 36 | }, [] 37 | ) 38 | 39 | /* ------------------------------------------------------------------------- 40 | Message management 41 | ------------------------------------------------------------------------- */ 42 | window.onmessage = async (event) => { 43 | if (event.data.pluginMessage.type === 'getAvatarURL') { 44 | if (event.data.pluginMessage.avatar) { 45 | fetchAvatar(event.data.pluginMessage.avatarLayer.id, event.data.pluginMessage.avatar, event.data.pluginMessage.avatar) 46 | } else { 47 | const url: string = `https://ogtojsonservice.vercel.app/api?url=${event.data.pluginMessage.config.ogurl}${event.data.pluginMessage.alias}`; 48 | fetch(url) 49 | .then((r) => r.json()) 50 | .then((a) => { 51 | if (a.ogImage && a.ogImage.url) { 52 | let ogurl: string = a.ogImage.url 53 | fetchAvatar(event.data.pluginMessage.avatarLayer.id, url, ogurl) 54 | } else { 55 | postMessage(event.data.pluginMessage.avatarLayer.id, '', []) 56 | } 57 | }) 58 | .catch((err) => { 59 | postMessage(event.data.pluginMessage.avatarLayer.id, '', []) 60 | console.error({ err }) 61 | }); 62 | } 63 | } 64 | } 65 | 66 | const fetchAvatar = async (layer: string, url: string, ogurl: string) => { 67 | fetch(ogurl) 68 | .then((r) => r.arrayBuffer()) 69 | .then((a) => { 70 | postMessage(layer, url, new Uint8Array(a)) 71 | }) 72 | .catch((err) => { 73 | postMessage(layer, '', []) 74 | console.error({ err }) 75 | }); 76 | } 77 | 78 | const postMessage = (layer: string, url: string, img: ArrayLike | ArrayBufferLike) => { 79 | parent.postMessage({ type: "message", pluginMessage: [{ 'layer': layer, 'url': url, 'from': 'getAvatarURL', 'img': img }] }, '*') 80 | } 81 | 82 | return ( 83 | 84 | 85 | Orgchart JSON or YAML – Learn more 86 | 87 | 94 | 95 | 96 | 99 | 103 | 104 | 105 | ) 106 | } 107 | 108 | export default render(Plugin) 109 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { convertHexColorToRgbColor, once, showUI } from '@create-figma-plugin/utilities' 2 | import { ChartConfig, CloseHandler, CreateChartHandler } from './types' 3 | import yaml from 'js-yaml' 4 | 5 | /** ------------------------------------------------------------------------- 6 | * Variables 7 | * ------------------------------------------------------------------------- */ 8 | 9 | /** 10 | * config: Default config object 11 | */ 12 | let config: ChartConfig = { 13 | avatar: true, 14 | name: true, 15 | alias: true, 16 | meta: true, 17 | ogurl: 'https://github.com/', 18 | color: { 19 | border: 'EEECF3', 20 | background: 'FFFFFF', 21 | primarytext: '444D56', 22 | secondarytext: 'A1A6AA', 23 | }, 24 | text: { 25 | label: { family: 'Helvetica Neue', style: 'Bold', size: 20 }, 26 | section: { family: 'Helvetica Neue', style: 'Bold', size: 18 }, 27 | name: { family: 'Helvetica Neue', style: 'Bold', size: 16 }, 28 | alias: { family: 'Helvetica Neue', style: 'Regular', size: 12 }, 29 | meta: { family: 'Helvetica Neue', style: 'Regular', size: 12 }, 30 | }, 31 | } 32 | 33 | /** 34 | * Declaring global color variables 35 | */ 36 | const fallbackColor: RGB = { r: 0, g: 0, b: 0 } 37 | let borderColor: RGB 38 | let primarytextColor: RGB 39 | let secondarytextColor: RGB 40 | let backgroundColor: RGB 41 | 42 | /** 43 | * Declaring global font promise variables 44 | */ 45 | let nameFont: Promise 46 | let aliasFont: Promise 47 | let metaFont: Promise 48 | let labelFont: Promise 49 | 50 | /** 51 | * Utility variables 52 | */ 53 | const signature = Date.now() 54 | let baseFrame: boolean = false 55 | let avatarsRequested: number = 0 56 | let cardComonent: ComponentNode 57 | let teamFrame: FrameNode 58 | let teamName: string 59 | 60 | /** ------------------------------------------------------------------------- 61 | * Functions 62 | * ------------------------------------------------------------------------- */ 63 | 64 | /** 65 | * Merges the default config object with the object passed as a parameter. 66 | * Sets up the color coniguration and the font promises. 67 | * 68 | * @param configuration - A config object 69 | * @returns The config object 70 | */ 71 | export function setConfiguration(configuration: ChartConfig) { 72 | if (configuration) { 73 | // Merge objects 74 | config = { 75 | ...config, 76 | ...configuration, 77 | color: { 78 | ...config.color, 79 | ...(configuration.color ? configuration.color : config.color), 80 | }, 81 | text: { 82 | ...config.text, 83 | ...(configuration.text ? configuration.text : config.text), 84 | }, 85 | } 86 | } 87 | 88 | // Set up colors 89 | borderColor = convertHexColorToRgbColor(config.color.border) || fallbackColor 90 | primarytextColor = convertHexColorToRgbColor(config.color.primarytext) || fallbackColor 91 | secondarytextColor = convertHexColorToRgbColor(config.color.secondarytext) || fallbackColor 92 | backgroundColor = convertHexColorToRgbColor(config.color.background) || fallbackColor 93 | 94 | // Load fonts 95 | nameFont = figma.loadFontAsync({ family: config.text.name.family, style: config.text.name.style }) 96 | aliasFont = figma.loadFontAsync({ family: config.text.alias.family, style: config.text.alias.style }) 97 | metaFont = figma.loadFontAsync({ family: config.text.meta.family, style: config.text.meta.style }) 98 | labelFont = figma.loadFontAsync({ family: config.text.label.family, style: config.text.label.style }) 99 | return config 100 | } 101 | 102 | /** 103 | * Generates the card component that we will use to clone later. 104 | * 105 | * @returns The card as a ComponentNode 106 | */ 107 | export async function createCardComponent() { 108 | // All fonts must be loaded before we can create the component 109 | const card = await Promise.all([nameFont, aliasFont, metaFont, labelFont]).then(() => { 110 | let cardComponent: ComponentNode = figma.currentPage.findAllWithCriteria({ types: ['COMPONENT'] }).filter((e) => e.name === 'FigmaOrgchartCard')[0] 111 | if (!cardComponent) { 112 | // Create the card component and 113 | cardComponent = figma.createComponent() 114 | cardComponent.name = 'FigmaOrgchartCard' 115 | cardComponent.fills = [{ type: 'SOLID', color: backgroundColor }] 116 | cardComponent.strokes = [{ type: 'SOLID', color: borderColor }] 117 | cardComponent.strokeAlign = 'INSIDE' 118 | cardComponent.strokeWeight = 2 119 | cardComponent.cornerRadius = 8 120 | cardComponent.resizeWithoutConstraints(280, 74) 121 | cardComponent.layoutMode = 'HORIZONTAL' 122 | cardComponent.layoutGrow = 0 123 | cardComponent.counterAxisAlignItems = 'CENTER' 124 | cardComponent.itemSpacing = 16 125 | cardComponent.paddingTop = 12 126 | cardComponent.paddingRight = 20 127 | cardComponent.paddingBottom = 12 128 | cardComponent.paddingLeft = 20 129 | 130 | // Create Avatar if enabled 131 | if (config.avatar) { 132 | const avatarComponent = figma.createEllipse() 133 | avatarComponent.name = 'Avatar' 134 | avatarComponent.resizeWithoutConstraints(40, 40) 135 | avatarComponent.fills = [{ type: 'SOLID', color: borderColor }] 136 | avatarComponent.strokes = [{ type: 'SOLID', color: fallbackColor, opacity: 0.12 }] 137 | avatarComponent.strokeAlign = 'INSIDE' 138 | avatarComponent.strokeWeight = 1 139 | cardComponent.appendChild(avatarComponent) 140 | } 141 | 142 | // Create text frame 143 | const cardComponentTextFrame = figma.createFrame() 144 | cardComponentTextFrame.name = 'TextFrame' 145 | cardComponentTextFrame.resizeWithoutConstraints(200, 74) 146 | cardComponentTextFrame.layoutMode = 'VERTICAL' 147 | cardComponentTextFrame.fills = [] 148 | cardComponent.appendChild(cardComponentTextFrame) 149 | 150 | // Create name textbox if enabled 151 | if (config.name) { 152 | const cardComponentName = createTextbox('', 'Name', config.text.name.family, config.text.name.style, config.text.name.size, primarytextColor) 153 | cardComponentName.layoutAlign = 'STRETCH' 154 | cardComponentTextFrame.appendChild(cardComponentName) 155 | } 156 | 157 | // Create alias textbox if enabled 158 | if (config.alias) { 159 | const cardComponentAlias = createTextbox('', 'Alias', config.text.alias.family, config.text.alias.style, config.text.alias.size, primarytextColor) 160 | cardComponentAlias.layoutAlign = 'STRETCH' 161 | cardComponentTextFrame.appendChild(cardComponentAlias) 162 | } 163 | 164 | // Create spacer 165 | const cardComponentSpacer = figma.createFrame() 166 | cardComponentSpacer.name = 'Spacer' 167 | cardComponentSpacer.resizeWithoutConstraints(4, 4) 168 | cardComponentSpacer.fills = [] 169 | cardComponentTextFrame.appendChild(cardComponentSpacer) 170 | 171 | // Create meta textbox if enabled 172 | if (config.meta) { 173 | const cardComponentMeta = createTextbox('', 'Meta', config.text.meta.family, config.text.meta.style, config.text.meta.size, secondarytextColor) 174 | cardComponentMeta.letterSpacing = { value: 0.3, unit: 'PIXELS' } 175 | cardComponentMeta.layoutAlign = 'STRETCH' 176 | cardComponentTextFrame.appendChild(cardComponentMeta) 177 | } 178 | } 179 | 180 | return cardComponent 181 | }) 182 | return card 183 | } 184 | 185 | /** 186 | * Creates a text box object with the content and the style we define. This method has way too many parameters, but it 187 | * helps to keep the code clean. 188 | * 189 | * @param label - The content of the text box 190 | * @param name - Name of the font 191 | * @param fontFamily - Font family that you want to use for the text. It should exist in Figma 192 | * @param fontStyle - Variation of the font family. It should exist in Figma 193 | * @param fontSize - Font size 194 | * @param color - Color of the text 195 | * @returns A text box object as TextNode 196 | */ 197 | export function createTextbox(label: string, name: string, fontFamily: string, fontStyle: string, fontSize: number, color: RGB): TextNode { 198 | const textbox = figma.createText() 199 | textbox.name = name 200 | textbox.fontName = { family: fontFamily, style: fontStyle } 201 | textbox.lineHeight = { value: 120, unit: 'PERCENT' } 202 | textbox.fills = [{ type: 'SOLID', color: color }] 203 | textbox.characters = label 204 | textbox.fontSize = fontSize 205 | textbox.textAlignHorizontal = 'LEFT' 206 | textbox.resizeWithoutConstraints(320, 10) 207 | textbox.textAutoResize = 'HEIGHT' 208 | 209 | return textbox 210 | } 211 | 212 | /** 213 | * Applies an image fill to a layer. 214 | * 215 | * @param layer - an image as a Uint8Array 216 | * @param imageData - an image as a Uint8Array 217 | * @returns an EllipseNode or RectangleNode 218 | */ 219 | export async function setBackgroundFill(layer: EllipseNode | RectangleNode, imageData: Uint8Array) { 220 | const imageHash = figma.createImage(new Uint8Array(imageData)).hash 221 | const imageFill: ImagePaint = { 222 | type: 'IMAGE', 223 | filters: { 224 | contrast: 0, 225 | exposure: 0, 226 | highlights: 0, 227 | saturation: 0, 228 | shadows: 0, 229 | temperature: 0, 230 | tint: 0, 231 | }, 232 | imageHash, 233 | imageTransform: [ 234 | [1, 0, 0], 235 | [0, 1, 0], 236 | ], 237 | opacity: 1, 238 | scaleMode: 'FILL', 239 | scalingFactor: 0.5, 240 | visible: true, 241 | } 242 | 243 | layer.fills = [imageFill] 244 | 245 | return layer 246 | } 247 | 248 | /** 249 | * Creates a frame for a team or a group of teams. 250 | * 251 | * @param teamName - the name of the team will be the name of the frame 252 | * @param key - it can be 'team' for a single team, or 'teams' for a group of teams 253 | * @returns a FrameNode 254 | */ 255 | export function createTeamFrame(teamName: string, key: string) { 256 | const frame = figma.createFrame() 257 | frame.name = teamName 258 | frame.primaryAxisSizingMode = 'AUTO' 259 | frame.counterAxisSizingMode = 'AUTO' 260 | frame.primaryAxisAlignItems = 'MIN' 261 | frame.counterAxisAlignItems = key === 'teams' ? 'MIN' : 'CENTER' 262 | frame.layoutMode = key === 'teams' ? 'HORIZONTAL' : 'VERTICAL' 263 | frame.itemSpacing = key === 'teams' ? 80 : 12 264 | frame.paddingTop = 16 265 | frame.paddingBottom = 32 266 | // if the frame is the first one, we add different padding 267 | if (!baseFrame) { 268 | frame.paddingTop = 80 269 | frame.paddingBottom = 80 270 | frame.paddingRight = 100 271 | frame.paddingLeft = 100 272 | } 273 | return frame 274 | } 275 | 276 | /** 277 | * Fill the contents of a card. 278 | * 279 | * @param card - the card to fill 280 | * @param name - string with the name of the person 281 | * @param alias - string with the alias of the person 282 | * @param meta - string with more information 283 | * @returns the transformed card 284 | */ 285 | export async function fillCardContent(card: ComponentNode | InstanceNode, name: string | undefined | null, alias: string | undefined | null, meta: string | undefined | null, avatar: string | undefined | null) { 286 | const textLayers: Array = card.findAllWithCriteria({ types: ['TEXT'] }) 287 | const avatarLayer: EllipseNode = card.findAllWithCriteria({ types: ['ELLIPSE'] }).filter((e) => e.name === 'Avatar')[0] 288 | 289 | // Update name 290 | if (config.name) { 291 | const nameLayer = textLayers.filter((text) => text.name === 'Name')[0] 292 | if (nameLayer && name) { 293 | nameLayer.characters = `${name}` 294 | } 295 | } 296 | 297 | // Update Alias 298 | if (config.alias) { 299 | const aliasLayer = textLayers.filter((text) => text.name === 'Alias')[0] 300 | if (aliasLayer && alias) { 301 | aliasLayer.characters = `${alias}` 302 | } 303 | } 304 | 305 | // Update meta 306 | if (config.meta) { 307 | const metaLayer = textLayers.filter((text) => text.name === 'Meta')[0] 308 | if (metaLayer && meta) { 309 | metaLayer.characters = `${meta}` 310 | } 311 | } 312 | 313 | if (config.avatar) { 314 | if ((alias && alias != '') || (avatar && avatar != '')) { 315 | figma.ui.postMessage({ type: 'getAvatarURL', alias, avatarLayer, config, avatar }) 316 | avatarsRequested = avatarsRequested + 1 317 | } 318 | } 319 | 320 | return card 321 | } 322 | 323 | /** 324 | * Process a key/value pair from the json. 325 | * 326 | * @param key - team, teams, manager, or members 327 | * @param value - the value of the key 328 | */ 329 | export async function process(key: any, value: any) { 330 | switch (key) { 331 | /* ------------------------------------------------------ 332 | Create layer for each team 333 | ------------------------------------------------------ */ 334 | case 'team': 335 | case 'teams': 336 | // Does the team layer already exist? 337 | teamName = `${key === 'team' ? value : teamFrame.name.replace(`team – ${signature}`, '')} ${key} – ${signature}` 338 | let teamLayer: FrameNode = figma.currentPage.findAllWithCriteria({ types: ['FRAME'] }).filter((node) => node.name === `${teamName}`)[0] 339 | // If it doesn't let's create it! 340 | if (teamLayer === undefined) { 341 | teamLayer = createTeamFrame(teamName, key) 342 | if (teamFrame) teamFrame.appendChild(teamLayer) 343 | baseFrame = true 344 | } 345 | 346 | teamFrame = teamLayer 347 | 348 | if (key === 'teams') { 349 | // Adding a shadow to act as a grouping line 350 | teamFrame.effects = [{ type: 'DROP_SHADOW', color: { r: 0, g: 0, b: 0, a: 0.2 }, offset: { x: 0, y: -1 }, radius: 0, visible: true, blendMode: 'MULTIPLY' }] 351 | 352 | // Create all the frames for each team 353 | value.forEach((e: any) => { 354 | const subteamLayer = createTeamFrame(`${e.team} team – ${signature}`, 'team') 355 | teamFrame.appendChild(subteamLayer) 356 | }) 357 | } else { 358 | // Create team textbox 359 | const teamName = teamFrame.name.replace(` team – ${signature}`, '') 360 | if (teamName != '') { 361 | const teamTextbox = createTextbox(teamName, 'Team', config.text.label.family, config.text.label.style, config.text.label.size, primarytextColor) 362 | teamLayer.appendChild(teamTextbox) 363 | } 364 | } 365 | 366 | break 367 | 368 | /* ------------------------------------------------------ 369 | Create manager entry 370 | ------------------------------------------------------ */ 371 | case 'manager': 372 | const manager = cardComonent.createInstance() 373 | manager.visible = true 374 | manager.name = `${value.name}` 375 | teamFrame.appendChild(manager) 376 | manager.resize(320, 74) 377 | // Fill the contents of the card 378 | fillCardContent(manager, value.name, value.alias, value.meta, value.avatar) 379 | 380 | break 381 | 382 | /* ------------------------------------------------------ 383 | Create designer entry 384 | ------------------------------------------------------ */ 385 | case 'members': 386 | // container for all the members 387 | const memberList = createTeamFrame('team members', 'team') 388 | memberList.resizeWithoutConstraints(320, 12) 389 | memberList.counterAxisSizingMode = 'FIXED' 390 | memberList.counterAxisAlignItems = 'MAX' 391 | memberList.paddingTop = memberList.paddingTop = 0 392 | memberList.itemSpacing = 2 393 | teamFrame.appendChild(memberList) 394 | 395 | value.forEach((d: any) => { 396 | if (d.section) { 397 | const sectionBox = createTextbox(`${d.section}`, 'Team', config.text.section.family, config.text.section.style, config.text.section.size, primarytextColor) 398 | sectionBox.resizeWithoutConstraints(296, config.text.section.size * 1.8) 399 | // sectionBox.textAutoResize = 'HEIGHT' 400 | sectionBox.textAlignVertical = 'CENTER' 401 | memberList.appendChild(sectionBox) 402 | } else { 403 | const designer = cardComonent.createInstance() 404 | designer.visible = true 405 | designer.name = `${d.name}` 406 | memberList.appendChild(designer) 407 | // Fill the contents of the card 408 | fillCardContent(designer, d.name, d.alias, d.meta, d.avatar) 409 | } 410 | }) 411 | 412 | break 413 | } 414 | } 415 | 416 | /** 417 | * Traverse the JSON. 418 | * 419 | * @param json - the json with the config and the chart data 420 | */ 421 | export async function traverse(json: any) { 422 | for (var i in json) { 423 | process(i, json[i]) 424 | if (json[i] !== null && typeof json[i] == 'object') { 425 | traverse(json[i]) 426 | } 427 | } 428 | } 429 | 430 | export default async function () { 431 | /* ------------------------------------------------------------------------- 432 | Create chart 433 | ------------------------------------------------------------------------- */ 434 | once('CREATE_CHART', async function (chartData: string) { 435 | // Handle messages 436 | figma.ui.on('message', (value) => { 437 | value = value[0] 438 | if (value.url !== '') { 439 | const avatarLayer: EllipseNode = figma.currentPage.findAllWithCriteria({ types: ['ELLIPSE'] }).filter((e) => e.id === value.layer)[0] 440 | setBackgroundFill(avatarLayer, value.img) 441 | } 442 | avatarsRequested = avatarsRequested - 1 443 | // Close the plugin once we have load all the avatars 444 | if (avatarsRequested === 0) { 445 | figma.closePlugin() 446 | } 447 | }) 448 | 449 | // Parse chart data 450 | // const chartDataObject = JSON.parse(chartData) 451 | const chartDataObject = yaml.load(chartData) as any 452 | 453 | // Read configuration 454 | setConfiguration(chartDataObject.config) 455 | 456 | // Create card component 457 | cardComonent = await createCardComponent() 458 | cardComonent.visible = false 459 | 460 | // Traverse JSON 461 | await traverse(chartDataObject).then(() => { 462 | // If we are not going to paint the avatar, let's close the plugin 463 | if (!config.avatar || avatarsRequested === 0) figma.closePlugin() 464 | }) 465 | }) 466 | 467 | /* ------------------------------------------------------------------------- 468 | Close Plugin 469 | ------------------------------------------------------------------------- */ 470 | once('CLOSE', function () { 471 | figma.closePlugin() 472 | }) 473 | 474 | /* ------------------------------------------------------------------------- 475 | Show UI window 476 | ------------------------------------------------------------------------- */ 477 | showUI({ 478 | width: 600, 479 | height: 440, 480 | }) 481 | } 482 | --------------------------------------------------------------------------------