├── .gitignore ├── images ├── .DS_Store ├── folder.png ├── icon@1x.png ├── icon@2x.png ├── decrement.png └── increment.png ├── src ├── ui │ ├── components │ │ ├── widgets_prefix_ui.js │ │ ├── numbers_method_name.js │ │ ├── exported_code_path_ui.js │ │ ├── export_to_ui.js │ │ ├── export_type_ui.js │ │ ├── export_with_checkboxs_ui.js │ │ ├── precision_row_ui.js │ │ ├── project_folder_ui.js │ │ ├── util.js │ │ ├── output_ui.js │ │ └── export_buttons_ui.js │ ├── css.js │ ├── dialogs │ │ ├── manifest.js │ │ └── dialogs.js │ └── main_panel_ui.js ├── widgets │ ├── stateless.js │ ├── group.js │ ├── grid.js │ ├── util │ │ ├── alignment_by_father.js │ │ ├── parameter.js │ │ ├── xd_alignment_to_dart_alignment.js │ │ ├── widgets_util.js │ │ ├── gradients.js │ │ ├── decorations.js │ │ ├── color.js │ │ └── google_fonts.js │ ├── mask.js │ ├── inkwell.js │ ├── artboard.js │ ├── component.js │ ├── container.js │ ├── svg.js │ ├── text.js │ └── children.js ├── bounds.js ├── project_folder.js ├── color.js ├── generate_path.js ├── generate.js ├── icon │ └── functions.js ├── export_all.js ├── util.js └── items_to_dart.js ├── CHANGELOG.md ├── main.js ├── manifest.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thize/xd-to-flutter/HEAD/images/.DS_Store -------------------------------------------------------------------------------- /images/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thize/xd-to-flutter/HEAD/images/folder.png -------------------------------------------------------------------------------- /images/icon@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thize/xd-to-flutter/HEAD/images/icon@1x.png -------------------------------------------------------------------------------- /images/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thize/xd-to-flutter/HEAD/images/icon@2x.png -------------------------------------------------------------------------------- /images/decrement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thize/xd-to-flutter/HEAD/images/decrement.png -------------------------------------------------------------------------------- /images/increment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thize/xd-to-flutter/HEAD/images/increment.png -------------------------------------------------------------------------------- /src/ui/components/widgets_prefix_ui.js: -------------------------------------------------------------------------------- 1 | function widgetsPrefixUi() { 2 | const title = '

Widget name prefix

'; 3 | const input = ''; 4 | return title + input; 5 | } 6 | 7 | module.exports = { 8 | widgetsPrefixUi: widgetsPrefixUi, 9 | }; 10 | -------------------------------------------------------------------------------- /src/ui/components/numbers_method_name.js: -------------------------------------------------------------------------------- 1 | function numbersMethodName() { 2 | const title = '

Numbers method name

'; 3 | const input = ''; 4 | return title + input; 5 | } 6 | 7 | module.exports = { 8 | numbersMethodName: numbersMethodName, 9 | }; 10 | -------------------------------------------------------------------------------- /src/ui/components/exported_code_path_ui.js: -------------------------------------------------------------------------------- 1 | function exportedCodePath() { 2 | const title = '

Exported Code Path

'; 3 | const input = ''; 4 | return title + input; 5 | } 6 | 7 | module.exports = { 8 | exportedCodePath: exportedCodePath, 9 | }; 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [3.3.3] - 2021-02-19 8 | ### Changed 9 | - fix Text Height 10 | - fix Export Artboard Color 11 | - fix Export All Colors 12 | - fix import app_colors.dart 13 | 14 | ### Removed 15 | - Simple Type 16 | - Group Name Comment -------------------------------------------------------------------------------- /src/ui/components/export_to_ui.js: -------------------------------------------------------------------------------- 1 | const { rowUi } = require("./util"); 2 | 3 | function exportToUi() { 4 | const title = '

Export to:

'; 5 | const normalType = rowUi(`Clipboard`); 6 | const classType = rowUi(`Dart File (Coming soon)`); 7 | return title + normalType + classType; 8 | } 9 | 10 | module.exports = { 11 | exportToUi: exportToUi, 12 | }; 13 | -------------------------------------------------------------------------------- /src/ui/components/export_type_ui.js: -------------------------------------------------------------------------------- 1 | const { rowUi } = require("./util"); 2 | 3 | function exportTypeUi() { 4 | const title = '

Export Selection Type

'; 5 | const normalType = rowUi(`Normal`); 6 | const classType = rowUi(`Stateless Widget`); 7 | return title + normalType + classType; 8 | } 9 | 10 | module.exports = { 11 | exportTypeUi: exportTypeUi, 12 | }; 13 | -------------------------------------------------------------------------------- /src/ui/components/export_with_checkboxs_ui.js: -------------------------------------------------------------------------------- 1 | const { rowUi } = require("./util"); 2 | 3 | function exportWithCheckBoxsUi() { 4 | const title = '

Export with

'; 5 | const checkboxs = 6 | prototypeInteractionsUi(); 7 | return title + checkboxs; 8 | } 9 | 10 | module.exports = { 11 | exportWithCheckBoxsUi: exportWithCheckBoxsUi, 12 | }; 13 | 14 | function prototypeInteractionsUi() { 15 | return rowUi(`Prototype Interactions`); 16 | } 17 | -------------------------------------------------------------------------------- /src/ui/css.js: -------------------------------------------------------------------------------- 1 | function build_css() { 2 | return ` 3 | 22 | `; 23 | } 24 | 25 | module.exports = { 26 | build_css: build_css, 27 | }; 28 | 29 | 30 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const { update, show } = require("./src/ui/main_panel_ui"); 2 | const { onTapGenerate } = require("./src/generate"); 3 | const { exportColor } = require("./src/color"); 4 | 5 | module.exports = { 6 | panels: { 7 | main_panel: { 8 | show, 9 | update 10 | } 11 | }, 12 | commands: { 13 | onTapGenerate: onTapGenerate, 14 | exportColor: exportColor 15 | } 16 | }; 17 | 18 | 19 | /* 20 | * Done 21 | ? Have to do 22 | ! To Do 23 | 24 | TODO: 25 | ! Android Adaptive icon 26 | ! Use TextStyle from Asset Panel 27 | ! Mask 28 | ! Repeat Grid 29 | ! List View 30 | */ 31 | -------------------------------------------------------------------------------- /src/ui/components/precision_row_ui.js: -------------------------------------------------------------------------------- 1 | const { customButtom, rowUi } = require("./util"); 2 | 3 | function precisionRowUi() { 4 | const title = '

Double Precision:

'; 5 | const decrementPrecisionButton = customButtom('decrementPrecisionButton'); 6 | const incrementPrecisionButton = customButtom('incrementPrecisionButton'); 7 | const precisionValue = '

2

'; 8 | const content = rowUi(decrementPrecisionButton + precisionValue + incrementPrecisionButton); 9 | return title + content; 10 | } 11 | 12 | module.exports = { 13 | precisionRowUi: precisionRowUi, 14 | }; 15 | -------------------------------------------------------------------------------- /src/ui/components/project_folder_ui.js: -------------------------------------------------------------------------------- 1 | const folderIconPath = '../../images/folder.png'; 2 | const { getFolderPath } = require('../../project_folder'); 3 | 4 | function projectFolderUi() { 5 | const title = '

Project Folder

'; 6 | const button = ``; 7 | const input = ` `; 8 | return title + button + input; 9 | } 10 | 11 | module.exports = { 12 | projectFolderUi: projectFolderUi, 13 | }; 14 | -------------------------------------------------------------------------------- /src/ui/components/util.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function rowUi(content) { 4 | return ``; 5 | } 6 | 7 | function buttonUi(id, title, active) { 8 | const button = ``; 9 | return rowUi(button); 10 | } 11 | 12 | function customButtom(id) { 13 | const folderIconPath = id == 'decrementPrecision' ? '../../images/decrement.png' : '../../images/increment.png'; 14 | const button = ``; 15 | return rowUi(button); 16 | } 17 | 18 | module.exports = { 19 | rowUi: rowUi, 20 | buttonUi: buttonUi, 21 | customButtom: customButtom, 22 | }; 23 | -------------------------------------------------------------------------------- /src/widgets/stateless.js: -------------------------------------------------------------------------------- 1 | const { cleanVarName } = require("./util/widgets_util"); 2 | 3 | class StatelessWidget { 4 | constructor(name, child) { 5 | this.name = name; 6 | this.child = child; 7 | } 8 | 9 | toDart() { 10 | this.name = widgetPrefix() + cleanVarName(this.name, true); 11 | return ` 12 | class ${this.name} extends StatelessWidget { 13 | const ${this.name}({Key key}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ${this.child} 18 | } 19 | }`; 20 | } 21 | } 22 | 23 | exports.StatelessWidget = StatelessWidget; 24 | 25 | function widgetPrefix() { 26 | const element = document.getElementById('widgetsPrexix'); 27 | const prefix = element != null ? element.value : element; 28 | if (!prefix) return ''; 29 | return prefix; 30 | } -------------------------------------------------------------------------------- /src/bounds.js: -------------------------------------------------------------------------------- 1 | class Bounds { 2 | /** 3 | * @param {Node} node 4 | * @param {number} x1 5 | * @param {number} x2 6 | * @param {number} y1 7 | * @param {number} y2 8 | */ 9 | constructor(node, parentBouds, x1, x2, y1, y2) { 10 | if (!node && !parentBouds) { 11 | this.x1 = x1 12 | this.x2 = x2; 13 | this.y1 = y1; 14 | this.y2 = y2; 15 | } else if (!node) { 16 | this.x1 = parentBouds.x1 17 | this.x2 = parentBouds.x2; 18 | this.y1 = parentBouds.y1; 19 | this.y2 = parentBouds.y2; 20 | } else { 21 | this.x1 = node.globalBounds.x; 22 | this.x2 = this.x1 + node.globalBounds.width; 23 | this.y1 = node.globalBounds.y; 24 | this.y2 = this.y1 + node.globalBounds.height; 25 | } 26 | } 27 | } 28 | 29 | exports.Bounds = Bounds; -------------------------------------------------------------------------------- /src/widgets/group.js: -------------------------------------------------------------------------------- 1 | const { Bounds } = require("../bounds"); 2 | 3 | class GroupWidget { 4 | constructor(xdNode) { 5 | this.xdNode = xdNode; 6 | this.bounds = new Bounds(xdNode); 7 | } 8 | 9 | toDart() { 10 | const { itemsToDart } = require("../items_to_dart"); 11 | const { removeItemsFromGroup } = require("../util"); 12 | const ungroupedItems = removeItemsFromGroup(this.xdNode.children); 13 | const itemsDart = itemsToDart(ungroupedItems); 14 | return itemsDart; 15 | // return `\n// Group: ${this.xdNode.name}\n${itemsDart}`; 16 | /* 17 | return ` 18 | Container( 19 | // Group: ${this.xdNode.name} 20 | width: ${this.xdNode.localBounds.width}, 21 | height: ${this.xdNode.localBounds.height}, 22 | child: ${itemsDart}, 23 | )`; 24 | */ 25 | } 26 | } 27 | 28 | exports.GroupWidget = GroupWidget; -------------------------------------------------------------------------------- /src/widgets/grid.js: -------------------------------------------------------------------------------- 1 | const { Bounds } = require("../bounds"); 2 | 3 | class GridWidget { 4 | constructor(xdNode) { 5 | this.xdNode = xdNode; 6 | this.bounds = new Bounds(xdNode); 7 | } 8 | 9 | toDart() { 10 | let withStyledWidget = document.querySelector('input[name="simpleType"]'); 11 | withStyledWidget = withStyledWidget != null ? withStyledWidget.checked : null; 12 | if (withStyledWidget) { 13 | return `Container( 14 | // [${this.xdNode.name}] Repeat grid aren't supported 15 | ).w(${this.xdNode.localBounds.width}).h(${this.xdNode.localBounds.height}).bgColor(Colors.red)`; 16 | } 17 | return ` 18 | Container( 19 | // [${this.xdNode.name}] Repeat grid aren't supported. 20 | width: ${this.xdNode.localBounds.width}, 21 | height: ${this.xdNode.localBounds.height}, 22 | color: Colors.red, 23 | )`; 24 | } 25 | 26 | } 27 | 28 | exports.GridWidget = GridWidget; -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Flutter Code Generator", 3 | "host": { 4 | "app": "XD", 5 | "minVersion": "21.0" 6 | }, 7 | "id": "b9d42c73", 8 | "icons": [ 9 | { 10 | "path": "images/icon@1x.png", 11 | "width": 24, 12 | "height": 24 13 | }, 14 | { 15 | "path": "images/icon@2x.png", 16 | "width": 48, 17 | "height": 48 18 | } 19 | ], 20 | "uiEntryPoints": [ 21 | { 22 | "type": "panel", 23 | "label": "UI Panel", 24 | "panelId": "main_panel" 25 | }, 26 | { 27 | "label": "Copy Selected", 28 | "type": "menu", 29 | "commandId": "onTapGenerate", 30 | "shortcut": { 31 | "mac": "Cmd+W", 32 | "win": "Ctrl+W" 33 | } 34 | }, 35 | { 36 | "label": "Copy Selected Item Color", 37 | "type": "menu", 38 | "commandId": "exportColor", 39 | "shortcut": { 40 | "mac": "Cmd+F", 41 | "win": "Ctrl+F" 42 | } 43 | } 44 | ], 45 | "version": "3.3.3" 46 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Giovani Mota Lobato 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/widgets/util/alignment_by_father.js: -------------------------------------------------------------------------------- 1 | const { xdAlignmentToDartAlignment } = require("./xd_alignment_to_dart_alignment"); 2 | 3 | function getAlignmentByFather(node, fatherNode, onlyTag) { 4 | const top = node.bounds.y1 - fatherNode.bounds.y1; 5 | const right = fatherNode.bounds.x2 - node.bounds.x2; 6 | const bot = fatherNode.bounds.y2 - node.bounds.y2; 7 | const left = node.bounds.x1 - fatherNode.bounds.x1; 8 | let auxBot = bot == 0 && top == 0 ? 1 : bot; 9 | const alignY = (top / (top + auxBot)); 10 | let auxRight = right == 0 && left == 0 ? 1 : right; 11 | const alignX = (left / (left + auxRight)); 12 | const resAlignment = xdAlignmentToDartAlignment(alignX, alignY); 13 | if (resAlignment == 'Alignment.center') { 14 | if (onlyTag) return `alignment: ${resAlignment},`; 15 | return `Center(child: ${node.toDart()},)`; 16 | } 17 | if (resAlignment != 'Alignment.topLeft') { 18 | if (onlyTag) return `alignment: ${resAlignment},`; 19 | return `Align(alignment: ${resAlignment}, child: ${node.toDart()},)`; 20 | } 21 | if (onlyTag) return ''; 22 | return node.toDart(); 23 | } 24 | 25 | exports.getAlignmentByFather = getAlignmentByFather; -------------------------------------------------------------------------------- /src/widgets/mask.js: -------------------------------------------------------------------------------- 1 | const { Bounds } = require("../bounds"); 2 | 3 | class MaskWidget { 4 | constructor(xdNode) { 5 | this.xdNode = xdNode; 6 | this.bounds = new Bounds(xdNode); 7 | } 8 | 9 | toDart() { 10 | // const { itemsToDart } = require("../items_to_dart"); 11 | // const { removeItemsFromGroup } = require("../util"); 12 | // const ungroupedItems = removeItemsFromGroup(this.xdNode.children); 13 | // const itemsDart = itemsToDart(ungroupedItems); 14 | let withStyledWidget = document.querySelector('input[name="simpleType"]'); 15 | withStyledWidget = withStyledWidget != null ? withStyledWidget.checked : null; 16 | if (withStyledWidget) { 17 | return `Container( 18 | // [${this.xdNode.name}] Group masks aren't supported. 19 | ).w(${this.xdNode.localBounds.width}).h(${this.xdNode.localBounds.height}).bgColor(Colors.red)`; 20 | } 21 | return ` 22 | Container( 23 | // [${this.xdNode.name}] Group masks aren't supported. 24 | width: ${this.xdNode.localBounds.width}, 25 | height: ${this.xdNode.localBounds.height}, 26 | color: Colors.red, 27 | )`; 28 | } 29 | } 30 | 31 | exports.MaskWidget = MaskWidget; -------------------------------------------------------------------------------- /src/widgets/inkwell.js: -------------------------------------------------------------------------------- 1 | const { Bounds } = require("../bounds"); 2 | 3 | class InkWellWidget { 4 | constructor(xdNode) { 5 | this.xdNode = xdNode; 6 | this.bounds = new Bounds(xdNode); 7 | } 8 | 9 | toDart(childWidget) { 10 | const { itemsToDart } = require("../items_to_dart"); 11 | const child = !childWidget ? itemsToDart(this.xdNode.children) : childWidget; 12 | let withInkWell = document.querySelector('input[name="prototypeInteractions"]'); 13 | withInkWell = withInkWell != null ? withInkWell.checked : null; 14 | if (!withInkWell) return child; 15 | let withStyledWidget = document.querySelector('input[name="simpleType"]'); 16 | withStyledWidget = withStyledWidget != null ? withStyledWidget.checked : null; 17 | if (withStyledWidget) { 18 | return `${child}.onTap((){ 19 | //TODO: onTap ${this.xdNode.name} 20 | print('onTap ${this.xdNode.name}'); 21 | })` 22 | } 23 | return ` 24 | InkWell( 25 | onTap: (){ 26 | //TODO: onTap ${this.xdNode.name} 27 | print('onTap ${this.xdNode.name}'); 28 | }, 29 | child: ${child}, 30 | )`; 31 | } 32 | } 33 | 34 | exports.InkWellWidget = InkWellWidget; -------------------------------------------------------------------------------- /src/widgets/util/parameter.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adobe 3 | All Rights Reserved. 4 | NOTICE: Adobe permits you to use, modify, and distribute this file in 5 | accordance with the terms of the Adobe license agreement accompanying 6 | it. If you have received this file from a source other than Adobe, 7 | then your use, modification, or distribution of it requires the prior 8 | written permission of Adobe. 9 | */ 10 | 11 | class Parameter { 12 | constructor(owner, type, name, value, optional) { 13 | this.owner = owner; 14 | this.type = type; 15 | this.name = name; 16 | this.value = value; 17 | this.optional = optional || false; 18 | } 19 | } 20 | 21 | exports.Parameter = Parameter; 22 | 23 | class ParameterRef { 24 | constructor(parameter, isOwn, exportName) { 25 | this.parameter = parameter; 26 | 27 | // Is own referes to if the value referenced in the parameter is to be serialized by the owning node 28 | // if false, some parent node will be responsible for serializing the value 29 | this.isOwn = isOwn; 30 | 31 | // Export name referes to the name of the parameter in dart code, if null this value is not to be 32 | // hoisted by a parent node 33 | this.exportName = exportName || null; 34 | } 35 | 36 | get name() { 37 | if (this.exportName) 38 | return this.exportName; 39 | else 40 | return this.parameter.name; 41 | } 42 | } 43 | 44 | exports.ParameterRef = ParameterRef; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Adobe XD Flutter Code Generator - Plugin 2 | 3 | 4 | ⚠️ **If you encounter an issue or have any feedback which you think could improve Plugin, please open an issue [here](https://github.com/thize/xd-to-flutter/issues)** 5 | 6 | ## About this Project 7 | 8 | The idea of the Plugin is: 9 | 10 | _“Increase productivity and decrease time spent with front-end code”._ 11 | 12 | ## Why? 13 | 14 | This project was designed to accelerate my company's software development and to help everyone waste time with simple and repetitive Widget creation processes.\ 15 | I will be happy if you can help me with as many PR's as you like so that we can perfect the plugin more and more 16 | 17 | Email-me: thize@bluebookapps.com 18 | 19 | Connect with me at [LinkedIn](https://linkedin.com/in/giovani-lobato-68aa57131). 20 | 21 | ### Installing 22 | 23 | Download xdx file from: https://github.com/thize/xd-to-flutter/releases 24 | 25 | or 26 | 27 | Install via Adobe Xd Plugin Center 28 | 29 | ## Contributing 30 | 31 | You can send how many PR's do you want, I'll be glad to analyse and accept them! And if you have any question about the project... 32 | 33 | Email-me: thizeml@gmail.com 34 | 35 | Connect with me at [LinkedIn](https://linkedin.com/in/giovani-lobato-68aa57131). 36 | 37 | Thank you! 38 | -------------------------------------------------------------------------------- /src/ui/components/output_ui.js: -------------------------------------------------------------------------------- 1 | function outputUi(two) { 2 | const title = '

Console:

'; 3 | if (two) { 4 | return title + `

Nothing...

`; 5 | } 6 | const outputText = `

Nothing...

`; 7 | return title + outputText; 8 | } 9 | 10 | function changeOutputUiText(newText, color, stop = false) { 11 | const redColor = 'LightCoral'; 12 | const greenColor = 'MediumSeaGreen'; 13 | const element = document.getElementById('outputText'); 14 | const element2 = document.getElementById('outputText2'); 15 | element.textContent = newText; 16 | element2.textContent = newText; 17 | if (!color) { 18 | element.style.color = greenColor; 19 | element2.style.color = greenColor; 20 | } else { 21 | if (color == 'red') { 22 | color = redColor; 23 | } 24 | element.style.color = color; 25 | element2.style.color = color; 26 | } 27 | if (stop) alert('error to stop the app'); 28 | } 29 | 30 | function getOutputUiText() { 31 | const element = document.getElementById('outputText'); 32 | if (!element) return ''; 33 | return element.textContent; 34 | } 35 | 36 | 37 | 38 | module.exports = { 39 | outputUi: outputUi, 40 | changeOutputUiText: changeOutputUiText, 41 | getOutputUiText: getOutputUiText, 42 | }; -------------------------------------------------------------------------------- /src/widgets/artboard.js: -------------------------------------------------------------------------------- 1 | const { Bounds } = require("../bounds"); 2 | const { getAlignmentByFather } = require("./util/alignment_by_father"); 3 | const { getGradientParam } = require("./util/gradients"); 4 | const xd = require("scenegraph"); 5 | const { getColor } = require("./util/color"); 6 | 7 | class ArtboardWidget { 8 | constructor(xdNode) { 9 | this.xdNode = xdNode; 10 | this.bounds = new Bounds(xdNode); 11 | } 12 | 13 | toDart(child) { 14 | const isFill = this.xdNode.fill instanceof xd.Color; 15 | const bgColor = isFill ? `backgroundColor: ${getColor(this.xdNode.fill, 1)},` : ''; 16 | let childWidget = ''; 17 | let alignment = ''; 18 | if (isFill) { 19 | if (child) childWidget = `body:${getAlignmentByFather(child, this)},`; 20 | } else { 21 | alignment = child ? getAlignmentByFather(child, this, true) : ''; 22 | if (child) childWidget = `child: ${child.toDart()},`; 23 | childWidget = ` 24 | body: Container( 25 | ${alignment} 26 | decoration: BoxDecoration( 27 | ${getGradientParam(this.xdNode.fill, 1)} 28 | ), 29 | ${childWidget} 30 | ), 31 | `; 32 | } 33 | return `Scaffold( 34 | ${bgColor} 35 | ${childWidget} 36 | )`; 37 | } 38 | } 39 | 40 | exports.ArtboardWidget = ArtboardWidget; -------------------------------------------------------------------------------- /src/widgets/component.js: -------------------------------------------------------------------------------- 1 | const { Bounds } = require("../bounds"); 2 | const { StatelessWidget } = require("./stateless"); 3 | const { formatDart } = require("./util/format_dart"); 4 | 5 | class ComponentWidget { 6 | constructor(xdNode) { 7 | this.xdNode = xdNode; 8 | this.bounds = new Bounds(xdNode); 9 | } 10 | 11 | toDart() { 12 | const { findMasterForSymbolId } = require("../util"); 13 | const { cleanVarName } = require("./util/widgets_util"); 14 | const master = findMasterForSymbolId(this.xdNode.symbolId); 15 | const componentName = !master ? this.xdNode.name : master.name; 16 | return `const ${cleanVarName(componentName, true)}()`; 17 | } 18 | 19 | toDartClass() { 20 | const { itemsToDart } = require("../items_to_dart"); 21 | const { findMasterForSymbolId } = require("../util"); 22 | const { wrapWithInkWell } = require("./util/widgets_util"); 23 | const master = findMasterForSymbolId(this.xdNode.symbolId); 24 | const componentName = !master ? this.xdNode.name : master.name; 25 | let dartComponent = itemsToDart(this.xdNode.children, false); 26 | dartComponent = wrapWithInkWell(this.xdNode, dartComponent); 27 | const { applyRegex } = require("../util"); 28 | dartComponent = formatDart(new StatelessWidget(componentName, dartComponent + ';').toDart()); 29 | return applyRegex(dartComponent); 30 | } 31 | } 32 | 33 | exports.ComponentWidget = ComponentWidget; -------------------------------------------------------------------------------- /src/ui/components/export_buttons_ui.js: -------------------------------------------------------------------------------- 1 | const { buttonUi } = require("./util"); 2 | 3 | function exportButtonsUi() { 4 | const titleAll = '

Export All:

'; 5 | const artboards = buildRadio('Artboards', true); 6 | const colors = buildRadio('Colors'); 7 | const components = buildRadio('Components'); 8 | const textStyles = buildRadio('TextStyles'); 9 | const switchs = artboards + components + '
' + colors + textStyles; 10 | const exportAllContent = titleAll + switchs + exportAllButton(); 11 | return exportAllContent; 12 | } 13 | 14 | function copyButtonsUi() { 15 | const title = '

Copy to clipboard:

'; 16 | const exportContent = title + exportSelectionButton() + exportSingleColorButton() + exportPathButton(); 17 | return exportContent; 18 | } 19 | 20 | 21 | 22 | function exportIconButtons() { 23 | const titleIcons = '

Export Icon:

'; 24 | const exportIcon = titleIcons + exportIosIconButton() + exportAndroidIconButton(); 25 | return exportIcon; 26 | } 27 | 28 | module.exports = { 29 | exportButtonsUi: exportButtonsUi, 30 | copyButtonsUi: copyButtonsUi, 31 | exportIconButtons: exportIconButtons, 32 | }; 33 | 34 | function exportSelectionButton() { 35 | return buttonUi('selectionButton', 'Selected Item', false); 36 | } 37 | 38 | function exportPathButton() { 39 | return buttonUi('pathButton', 'Selected Path', false); 40 | } 41 | 42 | function exportSingleColorButton() { 43 | return buttonUi('singleColorButton', 'Selected Item Color', false); 44 | } 45 | 46 | function exportIosIconButton() { 47 | return buttonUi('iosIconButton', 'iOS Icon', false); 48 | } 49 | 50 | function exportAndroidIconButton() { 51 | return buttonUi('androidIconButton', 'Android Icon', false); 52 | } 53 | 54 | function exportAllButton() { 55 | return buttonUi('exportAllButton', 'Export All', true); 56 | } 57 | 58 | function buildRadio(text, checked) { 59 | return ``; 60 | } -------------------------------------------------------------------------------- /src/widgets/util/xd_alignment_to_dart_alignment.js: -------------------------------------------------------------------------------- 1 | 2 | function xdAlignmentToDartAlignment(x, y) { 3 | const { fix } = require('../../util'); 4 | /// 0 to 1, have to be -1 to 1 5 | const dx = fix(fixAlignment(x)); 6 | const dy = fix(fixAlignment(y)); 7 | const align = `Alignment(${dx},${dy})`; 8 | const dif = Math.abs(dx - dy); 9 | if (dif < 0.02) return 'Alignment.center'; 10 | return nameAlignment[align] ? nameAlignment[align] : align; 11 | } 12 | 13 | const nameAlignment = { 14 | 'Alignment(-1.0,-1.0)': 'Alignment.topLeft', 15 | 'Alignment(1.0,-1.0)': 'Alignment.topRight', 16 | 'Alignment(0.0,-1.0)': 'Alignment.topCenter', 17 | 'Alignment(1.0,0.0)': 'Alignment.centerRight', 18 | 'Alignment(-1.0,0.0)': 'Alignment.centerLeft', 19 | 'Alignment(0.0,0.0)': 'Alignment.center', 20 | 'Alignment(1.0,1.0)': 'Alignment.bottomRight', 21 | 'Alignment(-1.0,1.0)': 'Alignment.bottomLeft', 22 | 'Alignment(0.0,1.0)': 'Alignment.bottomCenter', 23 | } 24 | 25 | function xdAlignmentToStyledDartAlignment(x, y) { 26 | const { fix } = require('../../util'); 27 | /// 0 to 1, have to be -1 to 1 28 | const dx = fix(fixAlignment(x)); 29 | const dy = fix(fixAlignment(y)); 30 | const align = `Alignment(${dx},${dy})`; 31 | const dif = Math.abs(dx - dy); 32 | if (dif < 0.02) return ['.center()']; 33 | return nameStyleAlignment[align] ? [nameStyleAlignment[align]] : [dx, dy]; 34 | } 35 | 36 | const nameStyleAlignment = { 37 | 'Alignment(-1.0,-1.0)': '.topLeft', 38 | 'Alignment(1.0,-1.0)': '.topRight', 39 | 'Alignment(0.0,-1.0)': '.topCenter', 40 | 'Alignment(1.0,0.0)': '.centerRight', 41 | 'Alignment(-1.0,0.0)': '.centerLeft', 42 | 'Alignment(0.0,0.0)': '.center()', 43 | 'Alignment(1.0,1.0)': '.bottomRight', 44 | 'Alignment(-1.0,1.0)': '.bottomLeft', 45 | 'Alignment(0.0,1.0)': '.bottomCenter', 46 | } 47 | 48 | exports.xdAlignmentToDartAlignment = xdAlignmentToDartAlignment; 49 | exports.xdAlignmentToStyledDartAlignment = xdAlignmentToStyledDartAlignment; 50 | 51 | /// 0 => -1 52 | /// 0.5 => 0 53 | /// 1 => 1 54 | /// Function = 2x - 1 55 | 56 | function fixAlignment(value) { 57 | return (2 * value) - 1; 58 | } -------------------------------------------------------------------------------- /src/project_folder.js: -------------------------------------------------------------------------------- 1 | const { changeOutputUiText } = require("./ui/components/output_ui"); 2 | const fs = require("uxp").storage.localFileSystem; 3 | 4 | let _projectFolder; 5 | 6 | function getFolderPath() { 7 | if (_projectFolder == null) { 8 | return ''; 9 | } 10 | return _projectFolder.nativePath; 11 | } 12 | 13 | function getFolder() { 14 | return _projectFolder; 15 | } 16 | 17 | async function changeProjectFolder() { 18 | let folder = await fs.getFolder(); 19 | if (!folder) { return; } 20 | _projectFolder = folder; 21 | } 22 | 23 | async function exportFiles(filesNames, filesContents, type) { 24 | if (!_projectFolder) { 25 | changeOutputUiText(`Project folder not selected`, 'red'); 26 | } 27 | const path = exportedCodePath(); 28 | let libFolder; 29 | try { 30 | libFolder = await _projectFolder.getEntry(name); 31 | } catch (error) { 32 | libFolder = await _getNestedF(_projectFolder, path.split('/')); 33 | } 34 | for (let i = 0; i < filesNames.length; i++) { 35 | const file = await libFolder.createFile(filesNames[i], { overwrite: true }); 36 | file.write(filesContents[i]); 37 | } 38 | changeOutputUiText(`Generated ${type} with Success`); 39 | } 40 | 41 | async function _getNestedF(parentF, names) { 42 | if (!parentF) { return null; } 43 | if (names.length == 0) return parentF; 44 | const name = names[0]; 45 | console.log(`_getNestedF = ${names.length}`); 46 | let f; 47 | try { 48 | f = await parentF.getEntry(name); 49 | names.splice(0, 1); 50 | } catch (e) { 51 | f = await parentF.createFolder(name); 52 | names.splice(0, 1); 53 | } 54 | return names.length > 0 ? _getNestedF(f, names) : f; 55 | } 56 | 57 | function exportedCodePath() { 58 | const element = document.getElementById('exportedCode'); 59 | const prefix = element != null ? element.value : ''; 60 | if (prefix == null || prefix == '') { 61 | return 'lib/xd'; 62 | } 63 | if (!prefix) return ''; 64 | return prefix; 65 | } 66 | 67 | module.exports = { 68 | getFolderPath: getFolderPath, 69 | getFolder: getFolder, 70 | exportFiles: exportFiles, 71 | changeProjectFolder: changeProjectFolder, 72 | }; 73 | -------------------------------------------------------------------------------- /src/widgets/util/widgets_util.js: -------------------------------------------------------------------------------- 1 | const { InkWellWidget } = require("../inkwell"); 2 | const { SvgWidget } = require("../svg"); 3 | 4 | function cleanVarName(name, isToCapitalize) { 5 | if (!name) { return ''; } 6 | name = name.replace(/^[\W\d]+|\W/ig, ''); 7 | if (isToCapitalize) { 8 | return capitalize(name); 9 | } 10 | return name; 11 | } 12 | 13 | exports.cleanVarName = cleanVarName; 14 | 15 | function capitalize(str) { 16 | return str[0].toUpperCase() + str.substr(1); 17 | } 18 | 19 | exports.capitalize = capitalize; 20 | 21 | function wrapWithInkWell(node, widget) { 22 | const isInkWell = !node ? false : node.triggeredInteractions[0] != null && node.triggeredInteractions[0].trigger.type == 'tap'; 23 | if (isInkWell) { 24 | widget = new InkWellWidget(node).toDart(widget); 25 | } 26 | return widget; 27 | } 28 | 29 | exports.wrapWithInkWell = wrapWithInkWell; 30 | 31 | function wrapWithRotation(node, widget) { 32 | if (node.widget instanceof SvgWidget) return widget; 33 | const thisRotation = node.widget.xdNode == null ? 0 : node.widget.xdNode.rotation; 34 | const fatherTotalRotation = !node.father ? 0 : getWidgetTotalRotation(node.father); 35 | const { fix } = require("../../util"); 36 | const rotation = fix(thisRotation - fatherTotalRotation); 37 | if (rotation == 0) return widget; 38 | return `Transform.rotate( 39 | angle: ${rotation} * pi / 180, 40 | child: ${widget}, 41 | )`; 42 | } 43 | 44 | exports.wrapWithRotation = wrapWithRotation; 45 | 46 | function getWidgetTotalRotation(node) { 47 | if (node.widget.xdNode == null) return 0; 48 | let totalRotation = node.widget.xdNode.rotation; 49 | return totalRotation; 50 | } 51 | 52 | exports.getWidgetTotalRotation = getWidgetTotalRotation; 53 | 54 | function titleCase(str) { 55 | str = str.replace( 56 | /\w\S*/g, 57 | function (txt) { 58 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); 59 | } 60 | ).split('\\N').join('\\n');; 61 | return str.replace( 62 | /\\n./g, 63 | function (txt) { 64 | return txt.substr(0, txt.length - 1) + txt.charAt(txt.length - 1).toUpperCase(); 65 | } 66 | ); 67 | } 68 | 69 | exports.titleCase = titleCase; -------------------------------------------------------------------------------- /src/color.js: -------------------------------------------------------------------------------- 1 | const { getColor } = require("./widgets/util/color"); 2 | const { exportDialog } = require("./ui/dialogs/dialogs"); 3 | const { Artboard } = require("scenegraph"); 4 | 5 | let clipboard = require("clipboard"); 6 | let scenegraph = require("scenegraph") 7 | 8 | let withErro; 9 | let color; 10 | 11 | const keydownFunc = function (event) { 12 | try { 13 | const key = event.key; 14 | if (key == 1) { 15 | color = exportFill(); 16 | } else if (key == 2) { 17 | color = exportStroke(); 18 | } else if (key == 3) { 19 | color = exportShadow(); 20 | } else if (key == 'Escape') { 21 | } else { 22 | withErro = 'invalidKey'; 23 | } 24 | } catch (error) { 25 | withErro = true; 26 | } 27 | if (color) clipboard.copyText(color); 28 | return false; 29 | }; 30 | 31 | async function exportColor() { 32 | withErro = null; 33 | color = null; 34 | const node = scenegraph.selection.items[0]; 35 | if ((node.children.length > 1 && !(node instanceof Artboard)) || scenegraph.selection.items.length > 1) { 36 | await exportDialog("Select only one item", 'Ok', 'Tap any key to close'); 37 | } else { 38 | document.addEventListener('keydown', keydownFunc); 39 | await exportDialog("Copy Item Color, Tap:", 'Cancelar', "1 to Fill
2 to Stroke
3 to Shadow"); 40 | document.removeEventListener('keydown', keydownFunc); 41 | if (withErro != null) { 42 | if (withErro == 'invalidKey') { 43 | await exportColor(); 44 | } else { 45 | await exportDialog("Error", 'Ok', 'Tap any key to close'); 46 | } 47 | } else { 48 | if (color != null) { 49 | await exportDialog("Success", 'Ok', 'Tap any key to close'); 50 | } 51 | } 52 | } 53 | } 54 | 55 | function exportFill() { 56 | const node = scenegraph.selection.items[0]; 57 | return getColor(node.fill); 58 | } 59 | 60 | function exportStroke() { 61 | const node = scenegraph.selection.items[0]; 62 | return getColor(node.stroke); 63 | } 64 | 65 | function exportShadow() { 66 | const node = scenegraph.selection.items[0]; 67 | return getColor(node.shadow.color); 68 | } 69 | 70 | module.exports = { 71 | exportColor: exportColor, 72 | }; -------------------------------------------------------------------------------- /src/generate_path.js: -------------------------------------------------------------------------------- 1 | const scenegraph = require("scenegraph"); 2 | const clipboard = require("clipboard"); 3 | const { changeOutputUiText } = require("./ui/components/output_ui"); 4 | const { formatDart } = require("./widgets/util/format_dart"); 5 | const { fix } = require("./util"); 6 | 7 | function onTapGeneratePath() { 8 | const item = scenegraph.selection.items[0]; 9 | const pathData = item.pathData; 10 | const lb = item.localBounds; 11 | const movs = []; 12 | const letters = new Set(); 13 | pathData.split(' ').forEach((e) => { 14 | if (e.match(/^[A-Za-z]+$/)) { 15 | movs.push(new Mov(e)); 16 | letters.add(e); 17 | } else { 18 | let value = parseFloat(e); 19 | movs[movs.length - 1].values.push(value); 20 | } 21 | }); 22 | // var letterStr = ''; 23 | // letters.forEach((letter) => { 24 | // letterStr += `${letter} - `; 25 | // }); 26 | // console.log(`Types = ${letterStr}`); 27 | movs.forEach((e) => { 28 | for (var i = 0; i < e.values.length; i++) { 29 | e.values[i] = fix((e.values[i] - (i % 2 == 0 ? lb.x : lb.y))); 30 | } 31 | }); 32 | let dartCode = `Path path = Path()`; 33 | movs.forEach((e) => { 34 | const t = e.type; 35 | const v = e.values; 36 | if (t == 'M') { 37 | // [x, y] 38 | dartCode += `..moveTo(${nmn(v[0])}, ${nmn(v[1])})` 39 | } else if (t == 'L') { 40 | // [x, y] 41 | dartCode += `..lineTo(${nmn(v[0])}, ${nmn(v[1])})` 42 | } else if (t == 'C') { 43 | // [x1, y1, x2, y2, x3, y3] 44 | dartCode += `..cubicTo(${nmn(v[0])}, ${nmn(v[1])}, ${nmn(v[2])}, ${nmn(v[3])}, ${nmn(v[4])}, ${nmn(v[5])})` 45 | } 46 | }); 47 | changeOutputUiText('Success'); 48 | dartCode = formatDart(dartCode + ';'); 49 | // console.log(`dartCode = ${dartCode}`); 50 | clipboard.copyText(dartCode); 51 | } 52 | 53 | exports.onTapGeneratePath = onTapGeneratePath; 54 | 55 | function nmn(value) { 56 | const element = document.getElementById('numbersMethodName'); 57 | let methodName = element != null ? element.value : element; 58 | methodName = methodName ? methodName : ''; 59 | if (methodName == '') return value; 60 | return `${methodName}(${value})`; 61 | } 62 | 63 | class Mov { 64 | constructor(type) { 65 | this.values = []; 66 | this.type = type; 67 | } 68 | } -------------------------------------------------------------------------------- /src/ui/dialogs/manifest.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | let manifest; 18 | 19 | /** 20 | * Reads the plugin's manifest and returns the parsed contents. 21 | * 22 | * Throws if the manifest is invalid or doesn't exist. 23 | * 24 | * Note: Reads manifest only once. Future calls will not reload 25 | * the manifest file. 26 | */ 27 | async function getManifest() { 28 | if (!manifest) { 29 | const fs = require("uxp").storage.localFileSystem; 30 | const dataFolder = await fs.getPluginFolder(); 31 | const manifestFile = await dataFolder.getEntry("manifest.json"); 32 | if (manifestFile) { 33 | const json = await manifestFile.read(); 34 | manifest = JSON.parse(json); 35 | } 36 | } 37 | return manifest; 38 | } 39 | 40 | /** 41 | * Return the icon path that can fit the requested size without upscaling. 42 | * 43 | * @param {*} manifest 44 | * @param {number} size 45 | * @returns {string} path to the icon 46 | */ 47 | function getNearestIcon(manifest, size) { 48 | if (!manifest) { 49 | return; 50 | } 51 | 52 | if (manifest.icons) { 53 | // icons is an array of objects of the form 54 | // { width, height, path } 55 | 56 | // icons are assumed to be square, so we'll sort descending on the width 57 | const sortedIcons = manifest.icons.sort((a, b) => { 58 | const iconAWidth = a.width; 59 | const iconBWidth = b.width; 60 | return iconAWidth < iconBWidth ? 1 : iconAWidth > iconBWidth ? -1 : 0; 61 | }); 62 | 63 | // next, search until we find an icon _too_ small for the desired size 64 | const icon = sortedIcons.reduce((last, cur) => { 65 | if (!last) { 66 | last = cur; 67 | } else { 68 | if (cur.width >= size) { 69 | last = cur; 70 | } 71 | } 72 | return last; 73 | }); 74 | 75 | return icon.path; 76 | } 77 | } 78 | 79 | module.exports = { 80 | getManifest, 81 | getNearestIcon 82 | } -------------------------------------------------------------------------------- /src/generate.js: -------------------------------------------------------------------------------- 1 | const scenegraph = require("scenegraph"); 2 | const clipboard = require("clipboard"); 3 | const { Artboard, SymbolInstance } = require("scenegraph"); 4 | const { itemsToDart } = require("./items_to_dart"); 5 | const { StatelessWidget } = require("./widgets/stateless"); 6 | const { formatDart } = require("./widgets/util/format_dart"); 7 | const { ComponentWidget } = require("./widgets/component"); 8 | const { listToString } = require("./util"); 9 | const { changeOutputUiText, getOutputUiText } = require("./ui/components/output_ui"); 10 | 11 | function onTapGenerate() { 12 | const items = scenegraph.selection.items; 13 | const hasSelection = items.length > 0; 14 | if (hasSelection) { 15 | changeOutputUiText('Nothing...', 'Grey'); 16 | const firstItem = items[0]; 17 | const isArtboard = firstItem instanceof Artboard; 18 | const isOnlyOneComponent = items.length == 1 && firstItem instanceof SymbolInstance; 19 | if (isOnlyOneComponent) { 20 | generateComponents(items, true); 21 | } else if (isArtboard) { 22 | generateArtboards(items); 23 | } else { 24 | generateSelection(items); 25 | } 26 | const text = getOutputUiText(); 27 | if (text == 'Nothing...') { 28 | changeOutputUiText('Success'); 29 | } 30 | } else { 31 | changeOutputUiText(`Nothing selected`, 'red'); 32 | } 33 | } 34 | 35 | exports.onTapGenerate = onTapGenerate; 36 | 37 | function generateComponents(components, isOnlyOneComponent = false) { 38 | const componentsWidget = []; 39 | components.forEach(component => { 40 | const dartCode = new ComponentWidget(component).toDartClass(); 41 | componentsWidget.push(dartCode); 42 | }); 43 | if (isOnlyOneComponent) { 44 | const dartCode = formatDart(listToString(componentsWidget)); 45 | clipboard.copyText(dartCode); 46 | } 47 | componentsWidget.forEach((c, i) => { 48 | componentsWidget[i] = formatDart(c); 49 | }); 50 | return componentsWidget; 51 | } 52 | 53 | exports.generateComponents = generateComponents; 54 | 55 | function generateArtboards(artboards, toClipboard = true) { 56 | let artboardsWidget = []; 57 | artboards.forEach(artboard => { 58 | const dartCode = new StatelessWidget(artboard.name, itemsToDart([artboard], true)).toDart(); 59 | artboardsWidget.push(dartCode); 60 | }); 61 | if (toClipboard) { 62 | const dartCode = formatDart(listToString(artboardsWidget)); 63 | clipboard.copyText(dartCode); 64 | } else { 65 | artboardsWidget.forEach((artboard, i) => { 66 | artboardsWidget[i] = formatDart(artboard); 67 | }); 68 | return artboardsWidget; 69 | } 70 | } 71 | 72 | exports.generateArtboards = generateArtboards; 73 | 74 | function generateSelection(items) { 75 | const dartCode = itemsToDart(items, true); 76 | clipboard.copyText(dartCode); 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/widgets/util/gradients.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adobe 3 | All Rights Reserved. 4 | 5 | NOTICE: Adobe permits you to use, modify, and distribute this file in 6 | accordance with the terms of the Adobe license agreement accompanying 7 | it. If you have received this file from a source other than Adobe, 8 | then your use, modification, or distribution of it requires the prior 9 | written permission of Adobe. 10 | */ 11 | 12 | // Serialization methods related to gradients 13 | 14 | const xd = require("scenegraph"); 15 | const assets = require("assets"); 16 | 17 | const { getColor } = require("./color"); 18 | const { xdAlignmentToDartAlignment } = require("./xd_alignment_to_dart_alignment"); 19 | 20 | function getGradientParam(fill, opacity) { 21 | let gradient = getGradient(fill, opacity); 22 | return gradient ? `gradient: ${gradient}, ` : ''; 23 | } 24 | exports.getGradientParam = getGradientParam; 25 | 26 | function getGradient(fill, opacity) { 27 | // Note: XD API docs say this should be called `LinearGradientFill` 28 | return fill instanceof xd.LinearGradient ? _getLinearGradient(fill, opacity) : 29 | fill instanceof xd.RadialGradient ? _getRadialGradient(fill, opacity) : 30 | ''; 31 | } 32 | exports.getGradient = getGradient; 33 | 34 | function getGradientFromAsset(xdColorAsset) { 35 | return `const ${getGradientTypeFromAsset(xdColorAsset)}(` + 36 | _getColorsParam(xdColorAsset.colorStops) + 37 | ')'; 38 | } 39 | exports.getGradientFromAsset = getGradientFromAsset; 40 | 41 | function getGradientTypeFromAsset(xdColorAsset) { 42 | return xdColorAsset.gradientType === assets.RADIAL ? "RadialGradient" : "LinearGradient"; 43 | } 44 | exports.getGradientTypeFromAsset = getGradientTypeFromAsset; 45 | 46 | function _getLinearGradient(fill, opacity = 1) { 47 | return 'LinearGradient(' + 48 | `begin: ${xdAlignmentToDartAlignment(fill.startX, fill.startY)},` + 49 | `end: ${xdAlignmentToDartAlignment(fill.endX, fill.endY)},` + 50 | _getColorsParam(fill.colorStops, opacity) + 51 | ')'; 52 | } 53 | 54 | function _getRadialGradient(fill, opacity = 1) { 55 | // RadialGradient is currently undocumented. It has the following properties: 56 | // startX/Y/R, endX/Y/R, colorStops, gradientTransform 57 | // XD currently does not seem to utilize endX/Y or startR, but basic support is included here. 58 | 59 | // Flutter always draws relative to the shortest edge, whereas XD draws the gradient 60 | // stretched to the aspect ratio of its container. 61 | const { fix } = require("../../util"); 62 | return 'RadialGradient(' + 63 | `center: ${xdAlignmentToDartAlignment(fill.startX, fill.startY)}, ` + 64 | (fill.startX === fill.endX && fill.startY === fill.endY ? '' : 65 | `focal: ${xdAlignmentToDartAlignment(fill.endX, fill.endY)}, `) + 66 | (fill.startR === 0 ? '' : `focalRadius: ${fix(fill.startR, 3)}, `) + 67 | `radius: ${fix(fill.endR, 3)}, ` + 68 | _getColorsParam(fill.colorStops, opacity) + 69 | _getTransformParam(fill) + 70 | ')'; 71 | } 72 | 73 | function _getColorsParam(arr, opacity) { 74 | const { fix } = require("../../util"); 75 | let l = arr.length, stops = [], colors = []; 76 | for (let i = 0; i < l; i++) { 77 | let s = arr[i]; 78 | stops.push(fix(s.stop, 3)); 79 | colors.push(getColor(s.color, opacity)); 80 | } 81 | const stopsS = stops.length > 2 ? `stops: [${stops.join(", ")}],` : ''; 82 | return `colors: [${colors.join(", ")}], ${stopsS}`; 83 | } 84 | 85 | function _getTransformParam(fill) { 86 | const { fix } = require("../../util"); 87 | // The GradientXDTransform is needed even if there is no transformation in order to fix the aspect ratio. 88 | let o = fill.gradientTransform; 89 | return 'transform: GradientXDTransform(' + 90 | `${fix(o.a, 3)}, ${fix(o.b, 3)}, ${fix(o.c, 3)}, ` + 91 | `${fix(o.d, 3)}, ${fix(o.e, 3)}, ${fix(o.f, 3)}, ` + 92 | `${xdAlignmentToDartAlignment(fill.startX, fill.startY)}), `; 93 | } 94 | -------------------------------------------------------------------------------- /src/widgets/container.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adobe 3 | All Rights Reserved. 4 | 5 | NOTICE: Adobe permits you to use, modify, and distribute this file in 6 | accordance with the terms of the Adobe license agreement accompanying 7 | it. If you have received this file from a source other than Adobe, 8 | then your use, modification, or distribution of it requires the prior 9 | written permission of Adobe. 10 | */ 11 | 12 | 13 | const { Bounds } = require("../bounds"); 14 | const { getAlignmentByFather } = require("./util/alignment_by_father"); 15 | const { getColorOrDecorationParam, getStyledDecoration } = require("./util/decorations"); 16 | const xd = require("scenegraph"); 17 | const { Parameter, ParameterRef } = require("./util/parameter"); 18 | const { getColor } = require("./util/color"); 19 | 20 | class ContainerWidget { 21 | constructor(xdNode) { 22 | this.xdNode = xdNode; 23 | this.bounds = new Bounds(xdNode); 24 | this.parameters = {}; 25 | 26 | let hasImageFill = (this.xdNode.fill instanceof xd.ImageFill); 27 | let fillParam = new Parameter(this, hasImageFill ? "ImageFill" : "Color", "fill", this.xdNode.fill); 28 | this.parameters["fill"] = new ParameterRef(fillParam, true, 29 | hasImageFill ? getProp(this.xdNode, 'imageParamName') : null); 30 | 31 | let strokeParam = new Parameter(this, "Color", "stroke", this.xdNode.stroke); 32 | this.parameters["stroke"] = new ParameterRef(strokeParam, true); 33 | 34 | let strokeEnableParam = new Parameter(this, "Boolean", "strokeEnabled", this.xdNode.strokeEnabled); 35 | this.parameters["strokeEnabled"] = new ParameterRef(strokeEnableParam, true); 36 | } 37 | 38 | toDart(child) { 39 | let withStyledWidget = document.querySelector('input[name="simpleType"]'); 40 | withStyledWidget = withStyledWidget != null ? withStyledWidget.checked : null; 41 | if (withStyledWidget) return this.styledWidget(this.xdNode, child, this.parameters); 42 | const alignment = child != null ? getAlignmentByFather(child, this, true) : ''; 43 | let childWidget = child != null ? `child:${child.toDart()},` : ``; 44 | let c = getColorOrDecorationParam(this.xdNode, this.parameters); 45 | return `Container( 46 | ${alignment} 47 | width: ${this.xdNode.localBounds.width}, 48 | height: ${this.xdNode.localBounds.height}, 49 | ${c} 50 | ${childWidget} 51 | )`; 52 | } 53 | 54 | styledWidget(xdNode, child, parameters) { 55 | const lb = xdNode.localBounds; 56 | let childWidget = child != null ? getStyledAlignmentByFather(child, this) : `boxWidget`; 57 | const { fix } = require("../util"); 58 | let w = fix(lb.width); 59 | let h = fix(lb.height); 60 | if (w == h) { 61 | w = `.sq(${w})`; 62 | h = ''; 63 | } else { 64 | w = `.w(${w})`; 65 | h = `.h(${h})`; 66 | } 67 | let d = ''; 68 | let noCorner = !(xdNode instanceof xd.Ellipse); 69 | if (noCorner) { 70 | const radii = xdNode.cornerRadii; 71 | const tl = radii.topLeft, tr = radii.topRight, br = radii.bottomRight, bl = radii.bottomLeft; 72 | noCorner = tl == 0 && tl === tr && tl === br && tl === bl; 73 | } 74 | if (!xdNode.strokeEnabled && noCorner && (xdNode.shadow == null || !xdNode.shadow.visible) && xdNode.fill instanceof xd.Color) { 75 | d = `.bgColor(${getColor(xdNode.fill)})`; 76 | } else { 77 | d = getStyledDecoration(xdNode, parameters); 78 | } 79 | return `${childWidget}${w}${h}${d}`; 80 | } 81 | } 82 | 83 | exports.ContainerWidget = ContainerWidget; 84 | 85 | function getProp(xdNode, prop) { 86 | let o = xdNode.pluginData; 87 | return o && o[prop]; 88 | } 89 | 90 | function getStyledAlignmentByFather(node, fatherNode) { 91 | const { xdAlignmentToStyledDartAlignment } = require("./util/xd_alignment_to_dart_alignment"); 92 | const top = node.bounds.y1 - fatherNode.bounds.y1; 93 | const right = fatherNode.bounds.x2 - node.bounds.x2; 94 | const bot = fatherNode.bounds.y2 - node.bounds.y2; 95 | const left = node.bounds.x1 - fatherNode.bounds.x1; 96 | let auxBot = bot == 0 && top == 0 ? 1 : bot; 97 | const alignY = (top / (top + auxBot)); 98 | let auxRight = right == 0 && left == 0 ? 1 : right; 99 | const alignX = (left / (left + auxRight)); 100 | const resAlignment = xdAlignmentToStyledDartAlignment(alignX, alignY); 101 | if (resAlignment[0] != '.topLeft') { 102 | if (resAlignment.length == 1) { 103 | return `${node.toDart()}${resAlignment[0]}`; 104 | } 105 | return `${node.toDart()}.alignment(${resAlignment[0]},${resAlignment[1]})`; 106 | } 107 | if (onlyTag) return ''; 108 | return node.toDart(); 109 | } 110 | -------------------------------------------------------------------------------- /src/ui/main_panel_ui.js: -------------------------------------------------------------------------------- 1 | const scenegraph = require("scenegraph"); 2 | const { build_css } = require("./css"); 3 | const { widgetsPrefixUi } = require("./components/widgets_prefix_ui"); 4 | const { outputUi, changeOutputUiText } = require("./components/output_ui"); 5 | const { projectFolderUi } = require("./components/project_folder_ui"); 6 | const { exportButtonsUi, copyButtonsUi, exportIconButtons } = require("./components/export_buttons_ui"); 7 | const { exportWithCheckBoxsUi } = require("./components/export_with_checkboxs_ui"); 8 | const { getFolderPath, changeProjectFolder } = require('../project_folder'); 9 | const { onTapGenerate } = require("../generate"); 10 | const { onTapGeneratePath } = require("../generate_path"); 11 | const { exportAppIcon } = require("../icon/functions"); 12 | const { numbersMethodName } = require("./components/numbers_method_name"); 13 | const { exportColor } = require("../color"); 14 | const { exportAll } = require("../export_all"); 15 | const { exportedCodePath } = require("./components/exported_code_path_ui"); 16 | 17 | 18 | let panel; 19 | 20 | function show(event) { 21 | if (!panel) { 22 | panel = document.createElement("div"); 23 | panel.innerHTML = 24 | build_css() + 25 | outputUi() + '
' + 26 | copyButtonsUi() + '
' + 27 | numbersMethodName() + '
' + 28 | exportWithCheckBoxsUi() + '
' + 29 | outputUi(true) + '
' + 30 | exportButtonsUi() + '
' + 31 | widgetsPrefixUi() + '
' + 32 | projectFolderUi() + '
' + 33 | exportedCodePath() + '
' + 34 | exportIconButtons() + '
'; 35 | event.node.appendChild(panel); 36 | buildTaps(); 37 | } 38 | } 39 | 40 | let oldItemsLengh; 41 | function update() { 42 | 43 | const items = scenegraph.selection.items; 44 | const singleColorButton = document.getElementById('singleColorButton'); 45 | const isToActiveSingleColorButton = items.length == 1 && (items[0].children.length == 0 || items[0].constructor.name == 'Artboard'); 46 | _changeButtonState(singleColorButton, isToActiveSingleColorButton); 47 | const selectionButton = document.getElementById('selectionButton'); 48 | const pathButton = document.getElementById('pathButton'); 49 | const iosIconButton = document.getElementById('iosIconButton'); 50 | const androidIconButton = document.getElementById('androidIconButton'); 51 | const isToActiveSelectionButton = items.length > 0; 52 | _changeButtonState(selectionButton, isToActiveSelectionButton); 53 | const isToActivePathButton = items.length == 1 && items[0].constructor.name == 'Path'; 54 | _changeButtonState(pathButton, isToActivePathButton); 55 | const isToActiveIosIconButton = items.length == 1; 56 | _changeButtonState(iosIconButton, isToActiveIosIconButton); 57 | const isToActiveAndroidIconButton = items.length == 1; 58 | _changeButtonState(androidIconButton, isToActiveAndroidIconButton); 59 | if (items.length == 0 && oldItemsLengh != 0) { 60 | changeOutputUiText('Nothing...', 'grey'); 61 | } 62 | oldItemsLengh = items.length; 63 | } 64 | 65 | function buildTaps() { 66 | let singleColorButton = document.getElementById('singleColorButton'); 67 | singleColorButton.onclick = _checkActive(singleColorButton, function () { 68 | exportColor(); 69 | }, function () { 70 | changeOutputUiText('Select one item', 'red'); 71 | }); 72 | 73 | let selectionButton = document.getElementById('selectionButton'); 74 | selectionButton.onclick = _checkActive(selectionButton, function () { 75 | onTapGenerate(); 76 | }, function () { 77 | changeOutputUiText(`Select something`, 'red'); 78 | }); 79 | let pathButton = document.getElementById('pathButton'); 80 | pathButton.onclick = _checkActive(pathButton, function () { 81 | onTapGeneratePath(); 82 | }, function () { 83 | changeOutputUiText(`Select one path`, 'red'); 84 | }); 85 | let exportAllButton = document.getElementById('exportAllButton'); 86 | exportAllButton.onclick = _checkActive(exportAllButton, function () { 87 | let exportAllRadio = document.querySelector('input[name="exportAllRadio"]:checked'); 88 | exportAll(exportAllRadio.value); 89 | }); 90 | let changeProjectFolderButton = document.getElementById('changeProjectFolderButton'); 91 | changeProjectFolderButton.onclick = async function () { 92 | await changeProjectFolder(); 93 | const projectFolderInput = document.getElementById('projectFolderInput'); 94 | projectFolderInput.value = getFolderPath(); 95 | }; 96 | let iosIconButton = document.getElementById('iosIconButton'); 97 | iosIconButton.onclick = function () { 98 | exportAppIcon('ios'); 99 | }; 100 | let androidIconButton = document.getElementById('androidIconButton'); 101 | androidIconButton.onclick = function () { 102 | exportAppIcon('android'); 103 | }; 104 | // let decrementPrecisionButton = document.getElementById('decrementPrecisionButton'); 105 | // decrementPrecisionButton.onclick = function () { 106 | // let value = parseInt(document.getElementById('incrementText').innerHTML); 107 | // value = value > 1 ? value - 1 : value; 108 | // document.getElementById('incrementText').innerHTML = value.toString(); 109 | // }; 110 | 111 | // let incrementPrecisionButton = document.getElementById('incrementPrecisionButton'); 112 | // incrementPrecisionButton.onclick = function () { 113 | // let value = parseInt(document.getElementById('incrementText').innerHTML); 114 | // value = value < 9 ? value + 1 : value; 115 | // document.getElementById('incrementText').innerHTML = value.toString(); 116 | // }; 117 | } 118 | 119 | function _checkActive(element, runFunction, elseFunction) { 120 | return function () { 121 | if (element.getAttribute('uxp-variant') == 'cta') { 122 | runFunction(); 123 | } else { 124 | if (elseFunction) elseFunction(); 125 | } 126 | } 127 | } 128 | 129 | function _changeButtonState(element, isToActive) { 130 | if (isToActive) { 131 | element.setAttribute("uxp-variant", "cta"); 132 | } else { 133 | element.setAttribute("uxp-variant", ""); 134 | } 135 | } 136 | 137 | module.exports = { 138 | show: show, 139 | update: update, 140 | }; 141 | -------------------------------------------------------------------------------- /src/ui/dialogs/dialogs.js: -------------------------------------------------------------------------------- 1 | const { getManifest, getNearestIcon } = require('./manifest.js'); 2 | 3 | let manifest; 4 | 5 | function strToHtml(str) { 6 | if (Array.isArray(str)) { 7 | return str.map(str => strToHtml(str)).join(''); 8 | } 9 | if (typeof str !== 'string') { 10 | return strToHtml(`${str}`); 11 | } 12 | 13 | let html = str; 14 | 15 | // handle some markdown stuff 16 | if (html.substr(0, 2) === '##') { 17 | html = `

${html.substr(2).trim().toUpperCase()}

`; 18 | } else if (html.substr(0, 1) === '#') { 19 | html = `

${html.substr(1).trim()}

`; 20 | } else if (html.substr(0, 2) === '* ') { 21 | html = `

${html.substr(2).trim()}

`; 22 | } else if (html.substr(0, 4) === '----') { 23 | html = `
${html.substr(5).trim()}`; 24 | } else if (html.substr(0, 3) === '---') { 25 | html = `
${html.substr(4).trim()}`; 26 | } else { 27 | html = `

${html.trim()}

`; 28 | } 29 | 30 | // handle links -- the catch here is that the link will transform the entire paragraph! 31 | const regex = /\[([^\]]*)\]\(([^\)]*)\)/; 32 | const matches = str.match(regex); 33 | if (matches) { 34 | const title = matches[1]; 35 | const url = matches[2]; 36 | html = `

${html.replace(regex, title).replace(/\<\|?p\>/g, '')}

`; 37 | } 38 | 39 | return html; 40 | } 41 | 42 | async function createDialog({ 43 | title, 44 | buttonText, 45 | icon = 'plugin-icon', 46 | msgs, 47 | prompt, 48 | multiline = false, 49 | render, 50 | template, 51 | isError = false, 52 | buttons = [ 53 | { label: 'Cancel', variant: 'cta', type: 'submit' } 54 | ] } = {}, 55 | width = 360, 56 | height = 'auto', 57 | iconSize = 18 58 | ) { 59 | 60 | let messages = Array.isArray(msgs) ? msgs : [msgs]; 61 | 62 | try { 63 | if (!manifest) { 64 | manifest = await getManifest(); 65 | } 66 | } catch (err) { 67 | // do nothing 68 | } 69 | 70 | let usingPluginIcon = false; 71 | if (icon === 'plugin-icon') { 72 | if (manifest.icons) { 73 | usingPluginIcon = true; 74 | iconSize = 24; 75 | icon = getNearestIcon(manifest, iconSize); 76 | } 77 | } 78 | 79 | const dialog = document.createElement('dialog'); 80 | dialog.innerHTML = ` 81 | 132 |
133 |

134 | ${title} 135 | ${icon ? `` : ''} 136 |

137 |
138 |
139 | ${ 140 | !render && ( 141 | template ? template() : ( 142 | messages.map(msg => strToHtml(msg)).join('') + 143 | (prompt ? `` : '') 148 | ) 149 | ) 150 | } 151 |
152 | 155 |
156 | `; 157 | 158 | // if render fn is passed, we'll call it and attach the DOM tree 159 | if (render) { 160 | dialog.querySelector(".container").appendChild(render()); 161 | } 162 | 163 | // The "ok" and "cancel" button indices. OK buttons are "submit" or "cta" buttons. Cancel buttons are "reset" buttons. 164 | // Ensure that the form can submit when the user presses ENTER (we trigger the OK button here) 165 | const form = dialog.querySelector('form'); 166 | form.onsubmit = () => dialog.close('ok'); 167 | 168 | // Attach button event handlers and set ok and cancel indices 169 | buttons.forEach(({ } = {}, idx) => { 170 | const button = dialog.querySelector(`#btn${idx}`); 171 | button.onclick = e => { 172 | e.preventDefault(); 173 | dialog.close('ok'); 174 | } 175 | }); 176 | const listener = function () { 177 | document.removeEventListener(listener); 178 | dialog.close(); 179 | }; 180 | document.addEventListener('keydown', listener); 181 | try { 182 | document.appendChild(dialog); 183 | await dialog.showModal(); 184 | } catch (err) { 185 | // system refused the dialog 186 | return { which: cancelButtonIdx, value: '' }; 187 | } finally { 188 | dialog.remove(); 189 | } 190 | } 191 | 192 | async function exportDialog(title, buttonText, ...msgs) { 193 | return createDialog({ title, buttonText, msgs, }); 194 | } 195 | 196 | module.exports = { 197 | createDialog, 198 | exportDialog 199 | }; -------------------------------------------------------------------------------- /src/icon/functions.js: -------------------------------------------------------------------------------- 1 | const scenegraph = require("scenegraph"); 2 | const application = require("application"); 3 | const commands = require("commands"); 4 | const { getFolder } = require("../project_folder"); 5 | const { Rectangle, Color, Group } = require("scenegraph"); 6 | const { changeOutputUiText } = require("../ui/components/output_ui"); 7 | 8 | async function exportAppIcon(platform) { 9 | const isAdaptiveAndroid = platform == 'adaptive-android'; 10 | const item = scenegraph.selection.items[0]; 11 | if (isAdaptiveAndroid && !_isValidAdaptiveItem(item)) { 12 | changeOutputUiText(`Not Valid Adaptive Icon, try export a Adaptive Icon Example`, 'red'); 13 | } else if (!item) { 14 | changeOutputUiText(`Select something`, 'grey'); 15 | } else if (!_is1024SquareItem(item)) { 16 | changeOutputUiText("Selected object is not 1024x1024.", 'red'); 17 | } else { 18 | const flutterProjectFolder = getFolder(); 19 | if (!flutterProjectFolder) { 20 | changeOutputUiText("Project folder not selected", 'red'); 21 | } else { 22 | new AppIcon(item, flutterProjectFolder).export(platform); 23 | } 24 | } 25 | } 26 | 27 | function generateAdaptiveIconExample() { 28 | const selection = scenegraph.selection; 29 | const background = new Rectangle(); 30 | background.width = 1024; 31 | background.height = 1024; 32 | background.fill = new Color("#8F8F8F"); 33 | background.name = 'Background'; 34 | const foreground = new Rectangle(); 35 | foreground.width = 626; 36 | foreground.height = 626; 37 | foreground.fill = new Color("#5E5C5C"); 38 | foreground.name = 'Foreground'; 39 | selection.insertionParent.addChild(background); 40 | selection.insertionParent.addChild(foreground); 41 | selection.items = [background, foreground]; 42 | commands.group(); 43 | let group = selection.items[0]; 44 | group.name = 'Adaptive Icon Example'; 45 | foreground.moveInParentCoordinates(199, 199); 46 | } 47 | 48 | module.exports = { 49 | exportAppIcon: exportAppIcon, 50 | generateAdaptiveIconExample: generateAdaptiveIconExample, 51 | }; 52 | 53 | function _isValidAdaptiveItem(item) { 54 | const isGroup = item instanceof Group; 55 | const hasTwoChildren = item.children.length == 2; 56 | try { 57 | const backgroundIs1024SquareItem = _is1024SquareItem(item.children.at(0)); 58 | const foregroundIsSmallerThan1024Square = _isSmallerThan1024Square(item.children.at(1)); 59 | return isGroup && hasTwoChildren && backgroundIs1024SquareItem && foregroundIsSmallerThan1024Square; 60 | } catch (error) { 61 | return false; 62 | } 63 | } 64 | 65 | function _is1024SquareItem(item) { 66 | return Math.round(item.localBounds.width) == 1024 && Math.round(item.localBounds.height) == 1024; 67 | } 68 | 69 | function _isSmallerThan1024Square(item) { 70 | return Math.round(item.localBounds.width) <= 1024 && Math.round(item.localBounds.height) <= 1024; 71 | } 72 | 73 | 74 | class AppIcon { 75 | constructor(item, flutterProjectFolder) { 76 | this.item = item; 77 | this.renditions = []; 78 | this.flutterProjectFolder = flutterProjectFolder; 79 | } 80 | 81 | async export(platform) { 82 | try { 83 | if (platform == 'ios') { 84 | await this.exportIosIcons(); 85 | } else if (platform == 'android') { 86 | await this.exportAndroidIcons(); 87 | } else if (platform == 'adaptive-android') { 88 | await this.exportAndroidAdaptiveIcons(); 89 | } 90 | } catch (error) { 91 | changeOutputUiText("Folder is not a Flutter Project", 'red'); 92 | } 93 | } 94 | 95 | async exportIosIcons() { 96 | const assetsFolder = await this.flutterProjectFolder.getEntry('ios/Runner/Assets.xcassets/AppIcon.appiconset'); 97 | const scales = [1024, 167, 20, 40, 60, 29, 58, 87, 40, 80, 120, 120, 180, 76, 152]; 98 | const names = ['1024x1024@1', '83.5x83.5@2', '20x20@1', '20x20@2', '20x20@3', '29x29@1', '29x29@2', '29x29@3', '40x40@1', '40x40@2', '40x40@3', '60x60@2', '60x60@3', '76x76@1', '76x76@2',]; 99 | for (let i = 0; i < Math.min(scales.length, names.length); i++) { 100 | const file = await assetsFolder.createFile(`Icon-App-${names[i]}x.png`, { overwrite: true }); 101 | const obj = this.generateRenditionObject(scales[i], file, true); 102 | this.renditions.push(obj); 103 | } 104 | this.createRenditions('iOS'); 105 | } 106 | 107 | async exportAndroidIcons() { 108 | const resFolder = await this.flutterProjectFolder.getEntry('android/app/src/main/res'); 109 | const entrys = [await resFolder.getEntry('mipmap-hdpi'), await resFolder.getEntry('mipmap-mdpi'), await resFolder.getEntry('mipmap-xhdpi'), await resFolder.getEntry('mipmap-xxhdpi'), await resFolder.getEntry('mipmap-xxxhdpi')]; 110 | const scales = [72, 48, 96, 144, 192]; 111 | for (let i = 0; i < entrys.length; i++) { 112 | const file = await entrys[i].createFile(`ic_launcher.png`, { overwrite: true }); 113 | const obj = this.generateRenditionObject(scales[i], file); 114 | this.renditions.push(obj); 115 | } 116 | this.createRenditions('Android'); 117 | } 118 | 119 | async exportAndroidAdaptiveIcons() { 120 | const mainFolder = await this.flutterProjectFolder.getEntry('android/app/src/main'); 121 | const resFolder = mainFolder.getEntry('res'); 122 | const entrys = [await resFolder.getEntry('mipmap-hdpi'), await resFolder.getEntry('mipmap-mdpi'), await resFolder.getEntry('mipmap-xhdpi'), await resFolder.getEntry('mipmap-xxhdpi'), await resFolder.getEntry('mipmap-xxxhdpi')]; 123 | const scales = [72, 48, 96, 144, 192]; 124 | for (let i = 0; i < entrys.length; i++) { 125 | const file = await entrys[i].createFile(`ic_launcher.png`, { overwrite: true }); 126 | const obj = this.generateRenditionObject(scales[i], file); 127 | this.renditions.push(obj); 128 | } 129 | // getEntry('mipmap-anydpi-v26'); dont exist, it have to be created 130 | const anyDpiFolder = await resFolder.getEntry('mipmap-anydpi-v26'); 131 | const icLauncherXml = await anyDpiFolder.createFile(`ic_launcher.xml`, { overwrite: true }); 132 | const icLauncherRoundXml = await anyDpiFolder.createFile(`ic_launcher.xml`, { overwrite: true }); 133 | const xml = ` 134 | 135 | 136 | 137 | `; 138 | icLauncherXml.write(xml); 139 | icLauncherRoundXml.write(xml); 140 | this.createRenditions('Android Adaptive'); 141 | } 142 | 143 | generateRenditionObject(scale, file, withoutAlpha) { 144 | let obj = {}; 145 | obj.node = this.item; 146 | obj.outputFile = file; 147 | obj.type = application.RenditionType.PNG; 148 | obj.scale = scale / 1024; 149 | if (withoutAlpha) { 150 | obj.background = new Color("#FFFFFF"); 151 | } 152 | return obj; 153 | } 154 | 155 | async createRenditions(platform) { 156 | try { 157 | await application.createRenditions(this.renditions); 158 | changeOutputUiText(`Generated ${platform} icons with Success`); 159 | } catch (err) { 160 | changeOutputUiText('Error when generating', 'red'); 161 | } 162 | } 163 | } -------------------------------------------------------------------------------- /src/widgets/util/decorations.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adobe 3 | All Rights Reserved. 4 | 5 | NOTICE: Adobe permits you to use, modify, and distribute this file in 6 | accordance with the terms of the Adobe license agreement accompanying 7 | it. If you have received this file from a source other than Adobe, 8 | then your use, modification, or distribution of it requires the prior 9 | written permission of Adobe. 10 | */ 11 | 12 | // Serialization methods related to Container BoxDecoration 13 | 14 | const xd = require("scenegraph"); 15 | 16 | const { getColor } = require("./color"); 17 | const { getGradientParam } = require("./gradients"); 18 | const { changeOutputUiText } = require("../../ui/components/output_ui"); 19 | 20 | /** BOXDECORATION */ 21 | function getColorOrDecorationParam(xdNode, parameters) { 22 | let noCorner = !(xdNode instanceof xd.Ellipse); 23 | if (noCorner) { 24 | const radii = xdNode.cornerRadii; 25 | const tl = radii.topLeft, tr = radii.topRight, br = radii.bottomRight, bl = radii.bottomLeft; 26 | noCorner = tl == 0 && tl === tr && tl === br && tl === bl; 27 | } 28 | if (!xdNode.strokeEnabled && noCorner && (xdNode.shadow == null || !xdNode.shadow.visible) && xdNode.fill instanceof xd.Color) { 29 | return _getFillParam(xdNode, parameters); 30 | } else { 31 | return getDecorationParam(xdNode, parameters); 32 | } 33 | } 34 | 35 | exports.getColorOrDecorationParam = getColorOrDecorationParam; 36 | 37 | function getDecorationParam(o, parameters) { 38 | return `decoration: ${_getBoxDecoration(o, parameters)}, `; 39 | } 40 | 41 | exports.getDecorationParam = getDecorationParam; 42 | 43 | 44 | function getStyledDecoration(xdNode, parameters) { 45 | const { fix, getOpacity } = require("../../util"); 46 | //! border 47 | let s = !xdNode.strokeEnabled ? '' : ` 48 | border: Border.all( 49 | width: ${xdNode.strokeWidth}, 50 | color: ${getColor(xdNode.stroke, getOpacity(xdNode))}, 51 | ),`; 52 | //! shadow 53 | let bs = xdNode.shadow; 54 | if (!bs || !bs.visible) { bs = ""; } else { 55 | bs = `boxShadow: [BoxShadow(color: ${getColor(bs.color, getOpacity(xdNode))}, offset: Offset(${fix(bs.x)}, ${fix(bs.y)}), blurRadius: ${fix(bs.blur)}, ), ], `; 56 | } 57 | //! radius 58 | let br = _getBorderRadiusParam(xdNode); 59 | //! Result 60 | return `.decoration( 61 | ${_getFillParam(xdNode, parameters)}${s}${bs}${br} 62 | )`; 63 | } 64 | 65 | exports.getStyledDecoration = getStyledDecoration; 66 | 67 | function _getBoxDecoration(xdNode, parameters) { 68 | const { getParamList } = require("../../util"); 69 | let str = getParamList([ 70 | _getBorderRadiusParam(xdNode, parameters), 71 | _getFillParam(xdNode, parameters), 72 | _getBorderParam(xdNode, parameters), 73 | _getBoxShadowParam(xdNode, parameters) 74 | ]); 75 | return "BoxDecoration(" + str + ")"; 76 | } 77 | 78 | /** FILL & STROKE */ 79 | function _getFillParam(xdNode, parameters) { 80 | if (!xdNode.fillEnabled || !xdNode.fill) { return ""; } 81 | let fill = xdNode.fill, blur = xdNode.blur; 82 | let fillOpacityFromBlur = (blur && blur.visible && blur.isBackgroundEffect) ? blur.fillOpacity : 1.0; 83 | const { getOpacity } = require("../../util"); 84 | let opacity = getOpacity(xdNode) * fillOpacityFromBlur; 85 | if (fill instanceof xd.Color) { 86 | let colorParameter = parameters["fill"].isOwn 87 | ? getColor(xdNode.fill, opacity) 88 | : parameters["fill"].name; 89 | return `color: ${colorParameter}, `; 90 | } 91 | if (fill instanceof xd.ImageFill) { 92 | return ` 93 | image: DecorationImage( 94 | image: AssetImage(''), 95 | fit: ${getBoxFit(fill.scaleBehavior)}, 96 | ${_getOpacityColorFilterParam(opacity)} 97 | ), 98 | `; 99 | } 100 | let gradient = getGradientParam(fill, opacity); 101 | if (gradient) { return gradient; } 102 | changeOutputUiText(`Unrecognized fill type ('${fill.constructor.name}').`, 'red'); 103 | } 104 | 105 | function _getOpacityColorFilterParam(opacity) { 106 | if (opacity >= 1) { return ''; } 107 | return `colorFilter: new ColorFilter.mode(Colors.black.withOpacity(${opacity}), BlendMode.dstIn), `; 108 | } 109 | 110 | function _getBorderParam(xdNode, parameters) { 111 | const isLine = xdNode instanceof xd.Line; 112 | if (!isLine && xdNode.strokeEnabled && xdNode.strokePosition !== xd.GraphicNode.INNER_STROKE) { 113 | changeOutputUiText('Only inner strokes are supported on rectangles & ellipses.', 'Brown'); 114 | } 115 | if (xdNode.strokeEnabled && xdNode.strokeJoins !== xd.GraphicNode.STROKE_JOIN_MITER) { 116 | changeOutputUiText('Only miter stroke joins are supported on rectangles & ellipses.', 'Brown'); 117 | } 118 | let dashes = xdNode.strokeDashArray; 119 | if (xdNode.strokeEnabled && dashes && dashes.length && dashes.reduce((a, b) => a + b)) { 120 | changeOutputUiText('Dashed lines are not supported on rectangles & ellipses.', 'Brown'); 121 | } 122 | let strokeEnableParamRef = parameters["strokeEnabled"]; 123 | let strokeEnableParam = strokeEnableParamRef.parameter; 124 | const { getOpacity } = require("../../util"); 125 | let strokeParam = parameters["stroke"].isOwn 126 | ? xdNode.stroke && getColor(xdNode.stroke, getOpacity(xdNode)) 127 | : parameters["stroke"].name; 128 | if (!strokeParam) { return ""; } 129 | 130 | if (strokeEnableParamRef.isOwn) { 131 | if (!xdNode.strokeEnabled || !xdNode.stroke) { return ""; } 132 | return `border: Border.all(width: ${xdNode.strokeWidth}, color: ${strokeParam},), `; 133 | } else { 134 | return `border: ${strokeEnableParam.name} ? Border.all(width: ${xdNode.strokeWidth}, color: ${strokeParam},) : null, `; 135 | } 136 | } 137 | 138 | 139 | /** BORDERRADIUS */ 140 | function _getBorderRadiusParam(o) { 141 | let radiusStr; 142 | if (o instanceof xd.Ellipse) { 143 | const x = o.radiusX; 144 | const y = o.radiusY; 145 | if (x == y) return 'shape: BoxShape.circle,'; 146 | radiusStr = _getBorderRadiusForEllipse(o); 147 | } else if (o.hasRoundedCorners) { 148 | radiusStr = _getBorderRadiusForRectangle(o); 149 | } 150 | return radiusStr ? `borderRadius: ${radiusStr}, ` : ""; 151 | } 152 | 153 | function _getBorderRadiusForEllipse(o) { 154 | return `BorderRadius.all(Radius.elliptical(${o.radiusX}, ${o.radiusY}))`; 155 | } 156 | 157 | function _getBorderRadiusForRectangle(o) { 158 | let radii = o.cornerRadii; 159 | let tl = radii.topLeft, tr = radii.topRight, br = radii.bottomRight, bl = radii.bottomLeft; 160 | if (tl === tr && tl === br && tl === bl) { 161 | let withStyledWidget = document.querySelector('input[name="simpleType"]'); 162 | withStyledWidget = withStyledWidget != null ? withStyledWidget.checked : null; 163 | if (withStyledWidget) return `${tl}.circularBorderRadius()`; 164 | return `BorderRadius.circular(${tl})`; 165 | } else { 166 | if ((tl == tr || (tl <= 1 && tr <= 1)) && (br == bl || (br <= 1 && bl <= 1))) { 167 | return `BorderRadius.vertical( 168 | ${_getRadiusParam("top", tl)} 169 | ${_getRadiusParam("bottom", br)} 170 | )`; 171 | } 172 | 173 | if ((tl == bl || (tl <= 1 && bl <= 1)) && (br == tr || (br <= 1 && tr <= 1))) { 174 | return `BorderRadius.horizontal( 175 | ${_getRadiusParam("left", tl)} 176 | ${_getRadiusParam("right", br)} 177 | )`; 178 | } 179 | return 'BorderRadius.only(' + 180 | _getRadiusParam("topLeft", tl) + 181 | _getRadiusParam("topRight", tr) + 182 | _getRadiusParam("bottomRight", br) + 183 | _getRadiusParam("bottomLeft", bl) + 184 | ')'; 185 | } 186 | } 187 | 188 | function _getRadiusParam(param, value) { 189 | if (value <= 1) { return ''; } 190 | let withStyledWidget = document.querySelector('input[name="simpleType"]'); 191 | withStyledWidget = withStyledWidget != null ? withStyledWidget.checked : null; 192 | if (withStyledWidget) return `${param}: ${value}.circularRadius(), `; 193 | return `${param}: Radius.circular(${value}), `; 194 | } 195 | 196 | 197 | /** SHADOWS */ 198 | function _getBoxShadowParam(xdNode) { 199 | const { getOpacity } = require("../../util"); 200 | let s = xdNode.shadow; 201 | if (!s || !s.visible) { return ""; } 202 | return `boxShadow: [BoxShadow(color: ${getColor(s.color, getOpacity(xdNode))}, offset: Offset(${s.x}, ${s.y}), blurRadius: ${s.blur}, ), ], `; 203 | } 204 | 205 | function getBoxFit(scaleBehavior) { 206 | return `BoxFit.${scaleBehavior === xd.ImageFill.SCALE_COVER ? 'cover' : 'fill'}`; 207 | } 208 | -------------------------------------------------------------------------------- /src/export_all.js: -------------------------------------------------------------------------------- 1 | const scenegraph = require("scenegraph"); 2 | const { generateArtboards } = require("./generate"); 3 | const { exportFiles } = require("./project_folder"); 4 | const { changeOutputUiText } = require("./ui/components/output_ui"); 5 | const { cleanVarName } = require("./widgets/util/widgets_util"); 6 | const { generateComponents } = require("./generate"); 7 | const { formatDart } = require("./widgets/util/format_dart"); 8 | const assets = require("assets"); 9 | const { Parameter, ParameterRef } = require("./widgets/util/parameter"); 10 | const { _getStyleParam, _getTextStyleParamList } = require("./widgets/text"); 11 | const { applyRegex } = require("./util"); 12 | const { getColor } = require("./widgets/util/color"); 13 | let components = []; 14 | let componentsNames = []; 15 | 16 | function exportAll(type) { 17 | if (type == 'Components') { 18 | exportAllComponents(); 19 | } else if (type == 'TextStyles') { 20 | exportAllTextStyles(); 21 | } else if (type == 'Colors') { 22 | exportAllColors(); 23 | } else { 24 | exportAllArtboards(); 25 | } 26 | } 27 | 28 | function exportAllArtboards() { 29 | getComponents(); 30 | const artboards = scenegraph.selection.editContext.children; 31 | const fileArtboardsName = []; 32 | const generatedArtboards = generateArtboards(artboards, false); 33 | artboards.forEach(artboard => { 34 | let name = cleanVarName(artboard.name, true); 35 | fileArtboardsName.push(`${name}.dart`); 36 | }); 37 | generatedArtboards.forEach((artboard, i) => { 38 | generatedArtboards[i] = getImports(artboard, componentsNames) + artboard; 39 | }); 40 | changeOutputUiText('Exporting Artboard Dart Files'); 41 | exportFiles(fileArtboardsName, generatedArtboards, 'Artboards'); 42 | } 43 | 44 | function exportAllComponents() { 45 | getComponents(); 46 | const fileComponentsName = []; 47 | const componentsWidgets = generateComponents(components); 48 | components.forEach(c => { 49 | const name = `${cleanVarName(c.name)}.dart`; 50 | fileComponentsName.push(name); 51 | }); 52 | if (componentsWidgets.length > 0) { 53 | componentsWidgets.forEach((c, i) => { 54 | componentsWidgets[i] = getImports(c, componentsNames) + c; 55 | }); 56 | changeOutputUiText('Exporting Components Dart Files'); 57 | exportFiles(fileComponentsName, componentsWidgets, 'Components'); 58 | } else { 59 | changeOutputUiText('No master components on Artboards', 'red'); 60 | } 61 | } 62 | 63 | function exportAllColors() { 64 | const assetsColors = assets.colors.get(); 65 | let resColors = ''; 66 | assetsColors.forEach((assetsColor, index) => { 67 | const name = assetsColor.name != null ? assetsColor.name : `color${index + 1}`; 68 | const generatedColor = _isGradient(assetsColor) ? _gradientColorList(assetsColor) : getColor(assetsColor.color, 1, false) 69 | const staticType = _isGradient(assetsColor) ? 'List' : 'Color'; 70 | const generatedStaticColor = `static const ${staticType} ${name} = ${generatedColor.replace('const ', '')};` 71 | resColors += generatedStaticColor + '\n'; 72 | }); 73 | if (resColors == '') { 74 | changeOutputUiText('Without Colors in Assets Panel', 'red'); 75 | } else { 76 | const prefix = widgetPrefix(); 77 | let appColorsClass = formatDart(`class ${prefix}AppColors {${resColors}}`); 78 | const materialImport = `import 'package:flutter/material.dart';\n\n`; 79 | appColorsClass = materialImport + appColorsClass; 80 | changeOutputUiText('Exporting app_colors.dart'); 81 | exportFiles(['app_colors.dart'], [appColorsClass], 'Colors'); 82 | } 83 | } 84 | 85 | function exportAllTextStyles() { 86 | const assetsTextStyles = assets.characterStyles.get(); 87 | let resStyles = ''; 88 | assetsTextStyles.forEach((assetsTextStyle, index) => { 89 | const name = assetsTextStyle.name != null ? assetsTextStyle.name : `textStyle${index + 1}`; 90 | const generatedTextStyle = _generateTextStyle(assetsTextStyle.style); 91 | const generatedStaticTextStyle = `static TextStyle get ${name} => const ${generatedTextStyle};` 92 | resStyles += generatedStaticTextStyle + '\n'; 93 | }); 94 | if (resStyles == '') { 95 | changeOutputUiText('Without Text Styles in Assets Panel', 'red'); 96 | } else { 97 | resStyles = applyRegex(resStyles); 98 | const prefix = widgetPrefix(); 99 | let appTextStylesClass = formatDart(`class ${prefix}AppTextStyles {${resStyles}}`); 100 | changeOutputUiText('Exporting app_textStyles.dart'); 101 | appTextStylesClass = getImportsTextStyle(appTextStylesClass) + appTextStylesClass; 102 | exportFiles(['app_textStyles.dart'], [appTextStylesClass], 'TextStyles'); 103 | } 104 | } 105 | 106 | 107 | function getImports(widget, componentsNames) { 108 | const material = `import 'package:flutter/material.dart';\n`; 109 | const svg = widget.includes('SvgPicture.string') ? `import 'package:flutter_svg/flutter_svg.dart';\n` : ''; 110 | const googleFonts = widget.includes('GoogleFonts.') ? `import 'package:google_fonts/google_fonts.dart';\n` : ''; 111 | const math = widget.includes('Transform.rotate') ? `import 'dart:math';\n` : ''; 112 | const simplecode = widget.includes('sz(') ? `import 'package:simple_code/simple_code.dart';\n` : ''; 113 | const appColors = widget.includes('AppColors.') ? `import 'app_colors.dart';\n` : ''; 114 | let importsComponents = ''; 115 | componentsNames.forEach(c => { 116 | if (widget.includes(`const ${c}`) && !widget.includes(`class ${c}`)) { 117 | importsComponents += `import '${c}.dart';\n`; 118 | } 119 | }); 120 | return material + svg + googleFonts + math + simplecode + appColors + importsComponents + '\n'; 121 | } 122 | 123 | function getImportsTextStyle(widget) { 124 | const material = `import 'package:flutter/material.dart';\n`; 125 | const googleFonts = widget.includes('GoogleFonts.') ? `import 'package:google_fonts/google_fonts.dart';\n` : ''; 126 | return material + googleFonts + '\n'; 127 | } 128 | 129 | function getComponents() { 130 | components = []; 131 | const usedComponentsId = []; 132 | const artboards = scenegraph.selection.editContext.children; 133 | artboards.forEach(async artboard => { 134 | let isArtboard = artboard.constructor.name == "Artboard"; 135 | if (isArtboard) { 136 | getComponentsFromGroup(artboard, components, usedComponentsId); 137 | } 138 | }); 139 | componentsNames = []; 140 | components.forEach(c => { 141 | componentsNames.push(cleanVarName(c.name)); 142 | }); 143 | } 144 | 145 | function getComponentsFromGroup(group, components, usedComponentsId) { 146 | group.children.forEach(async child => { 147 | const childName = child.constructor.name; 148 | const isSymbolInstance = childName == "SymbolInstance"; 149 | const isNotIncluded = !usedComponentsId.includes(child.symbolId); 150 | const isMaster = child.isMaster; 151 | const isComponentMaster = isSymbolInstance && isNotIncluded && isMaster; 152 | if (isComponentMaster) { 153 | usedComponentsId.push(child.symbolId); 154 | components.push(child); 155 | getComponentsFromGroup(child, components, usedComponentsId); 156 | } else if (childName == "Group") { 157 | getComponentsFromGroup(child, components, usedComponentsId); 158 | } 159 | }); 160 | } 161 | 162 | module.exports = { 163 | exportAll: exportAll, 164 | }; 165 | 166 | function widgetPrefix() { 167 | const element = document.getElementById('widgetsPrexix'); 168 | const prefix = element != null ? element.value : element; 169 | if (!prefix) return ''; 170 | return prefix; 171 | } 172 | 173 | 174 | function _generateTextStyle(xdNode) { 175 | let parameters = {}; 176 | 177 | let colorParam = new Parameter(this, "Color", "fill", xdNode.fill); 178 | parameters["fill"] = new ParameterRef(colorParam, true, 'teste'); 179 | 180 | return _getStyleParam(xdNode, _getTextStyleParamList(xdNode, null, parameters), false); 181 | } 182 | 183 | 184 | function _isGradient(fill) { 185 | return fill.startY != null || (fill.colorStops != null && fill.colorStops.length > 0); 186 | } 187 | 188 | function _gradientColorList(gradient) { 189 | let colors = []; 190 | gradient.colorStops.forEach(colorStop => { 191 | colors.push(getColor(colorStop.color, 1, false)); 192 | }); 193 | return `[${colors}]`; 194 | } 195 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | const scenegraph = require("scenegraph"); 2 | const { Path, Line, Group, Polygon, BooleanGroup, Artboard, RepeatGrid, SymbolInstance, Text } = require("scenegraph"); 3 | const { ArtboardWidget } = require("./widgets/artboard"); 4 | const { ComponentWidget } = require("./widgets/component"); 5 | const { ContainerWidget } = require("./widgets/container"); 6 | const { GroupWidget } = require("./widgets/group"); 7 | const { GridWidget } = require("./widgets/grid"); 8 | const { SvgWidget } = require("./widgets/svg"); 9 | const { TextWidget } = require("./widgets/text"); 10 | const { MaskWidget } = require("./widgets/mask"); 11 | 12 | function fix(num, digits = 2) { 13 | let p = Math.pow(10, digits); 14 | num = Math.round(num * p) / p; 15 | return num + (num === (num | 0) ? '.0' : ''); 16 | } 17 | 18 | exports.fix = fix; 19 | 20 | function _isSvgLine(item) { 21 | return item instanceof Line && item.globalBounds.width != 0 && item.globalBounds.height != 0; 22 | } 23 | 24 | function isSvgFolder(item) { 25 | let onlySvg = true; 26 | if (item.name.includes('svg_')) return onlySvg; 27 | item.children.forEach(child => { 28 | if (onlySvg) { 29 | // if (child instanceof Group) { 30 | // onlySvg = isSvgFolder(child); 31 | // } else { 32 | const isPath = child instanceof Path; 33 | const isPolygon = child instanceof Polygon; 34 | const isBooleanGroup = child instanceof BooleanGroup; 35 | const isSvgLine = _isSvgLine(child); 36 | onlySvg = (isPath || isPolygon || isBooleanGroup || isSvgLine); 37 | // } 38 | } 39 | }); 40 | return onlySvg; 41 | } 42 | 43 | exports.isSvgFolder = isSvgFolder; 44 | 45 | function hasInteraction(item) { 46 | const hasInteraction = item.triggeredInteractions[0] != null && item.triggeredInteractions[0].trigger.type == 'tap'; 47 | return hasInteraction; 48 | } 49 | 50 | exports.hasInteraction = hasInteraction; 51 | 52 | function xdItemToWidget(item) { 53 | const isGrid = item instanceof RepeatGrid; 54 | if (isGrid) return new GridWidget(item); 55 | const isSvg = (item instanceof Group && isSvgFolder(item)) || item instanceof Path || item instanceof Polygon || item instanceof BooleanGroup || _isSvgLine(item); 56 | if (isSvg) return new SvgWidget(item); 57 | const isGroup = item instanceof Group; 58 | const isMaskGroup = isGroup && item.mask; 59 | if (isMaskGroup) return new MaskWidget(item); 60 | if (isGroup) return new GroupWidget(item); 61 | const isComponent = item instanceof SymbolInstance; 62 | if (isComponent) return new ComponentWidget(item); 63 | const isArtboard = item instanceof Artboard; 64 | if (isArtboard) return new ArtboardWidget(item); 65 | const isText = item instanceof Text; 66 | if (isText) return new TextWidget(item); 67 | return new ContainerWidget(item); 68 | } 69 | 70 | exports.xdItemToWidget = xdItemToWidget; 71 | 72 | function widgetCanHaveChild(widget) { 73 | const isGrid = widget instanceof GridWidget; 74 | const isSvg = widget instanceof SvgWidget; 75 | const isComponent = widget instanceof ComponentWidget; 76 | const isText = widget instanceof TextWidget; 77 | const isGroup = widget instanceof GroupWidget; 78 | const isAWidgetThatCannotHaveChild = isGrid || isSvg || isComponent || isText || isGroup; 79 | return !isAWidgetThatCannotHaveChild; 80 | } 81 | 82 | exports.widgetCanHaveChild = widgetCanHaveChild; 83 | 84 | function removeItemsFromGroup(items) { 85 | let removedItems = []; 86 | items.forEach(item => { 87 | const isGroup = item instanceof Group; 88 | const isArtboard = item instanceof Artboard; 89 | const isSvgGroup = isGroup && (item.name.includes('svg_') || isSvgFolder(item)); 90 | const isMaskGroup = isGroup && item.mask; 91 | if (isSvgGroup || isMaskGroup) { 92 | removedItems.push(item); 93 | } else if (isArtboard /* || isGroup */) { 94 | // if (isArtboard) { 95 | removedItems.push(item); 96 | // } 97 | removeItemsFromGroup(item.children).forEach(child => { 98 | removedItems.push(child); 99 | }); 100 | } else { 101 | removedItems.push(item); 102 | } 103 | }); 104 | return removedItems; 105 | } 106 | 107 | exports.removeItemsFromGroup = removeItemsFromGroup; 108 | 109 | function findMasterForSymbolId(symbolId, xdNode) { 110 | let result = null; 111 | if (!xdNode) { 112 | xdNode = scenegraph.selection.editContext; 113 | } 114 | if (xdNode instanceof SymbolInstance) { 115 | if (xdNode.isMaster && xdNode.symbolId === symbolId) { 116 | result = xdNode; 117 | } 118 | } 119 | xdNode.children.forEach((child) => { 120 | if (!result) result = findMasterForSymbolId(symbolId, child); 121 | }); 122 | return result; 123 | } 124 | 125 | exports.findMasterForSymbolId = findMasterForSymbolId; 126 | 127 | function listToString(list) { 128 | var string = ''; 129 | list.forEach(item => { 130 | string += '\n' + item; 131 | }); 132 | return string; 133 | } 134 | 135 | exports.listToString = listToString; 136 | 137 | function getOpacity(xdNode) { 138 | let o = xdNode, opacity = 1.0; 139 | while (o) { 140 | if (o.opacity != null) { opacity *= o.opacity; } 141 | o = o.parent; 142 | } 143 | return opacity; 144 | } 145 | 146 | exports.getOpacity = getOpacity; 147 | 148 | function applyRegex(str) { 149 | const getNumberRegex = '[0-9]+([\\.][0-9]+)?'; 150 | str = _applySCRegexWithTag(str, getNumberRegex, 'width'); 151 | str = _applySCRegexWithTag(str, getNumberRegex, 'height'); 152 | str = _applySCRegexWithTag(str, getNumberRegex, 'fontSize'); 153 | str = _applySCRegexWithTag(str, getNumberRegex, 'blurRadius'); 154 | str = _applySCRegexWithTag(str, getNumberRegex, 'right'); 155 | str = _applySCRegexWithTag(str, getNumberRegex, 'left'); 156 | str = _applySCRegexWithTag(str, getNumberRegex, 'top'); 157 | str = _applySCRegexWithTag(str, getNumberRegex, 'bottom'); 158 | str = _applySCRegexWithTag(str, getNumberRegex, null, 'Offset'); 159 | str = _applySCRegexWithTag(str, getNumberRegex, null, 'elliptical'); 160 | str = _applySCRegexWithTag(str, getNumberRegex, null, 'circular'); 161 | const element = document.getElementById('numbersMethodName'); 162 | let methodName = element != null ? element.value : element; 163 | methodName = methodName ? methodName : ''; 164 | if (methodName != "") { 165 | str = _applyTextStyle(str, methodName, 'TextStyle'); 166 | str = _applyTextStyle(str, methodName, 'GoogleFonts'); 167 | } 168 | return str; 169 | } 170 | 171 | function _applyTextStyle(str, methodName, tag) { 172 | let indexOf = str.indexOf(tag); 173 | while (indexOf != -1) { 174 | let ini = indexOf + 10; 175 | let index = ini; 176 | let qtdParentheses = 1; 177 | let end; 178 | while (end == null) { 179 | if (str[index] == '(') { 180 | qtdParentheses++ 181 | } else if (str[index] == ')') { 182 | qtdParentheses--; 183 | } 184 | if (qtdParentheses == 0) { 185 | end = index; 186 | } 187 | index++; 188 | } 189 | let fix = str.substring(ini, end); 190 | fix = fix.replace(new RegExp(`height: ${methodName}\(.*\)`, 'gm'), (value) => { 191 | return value.replace(`${methodName}(`, '').replace(')', ''); 192 | }); 193 | str = str.substring(0, ini) + fix + str.substring(end) 194 | indexOf = str.indexOf(tag, end); 195 | } 196 | return str; 197 | } 198 | 199 | exports.applyRegex = applyRegex; 200 | 201 | function _applySCRegexWithTag(str, regex, tag, method) { 202 | const element = document.getElementById('numbersMethodName'); 203 | let methodName = element != null ? element.value : element; 204 | methodName = methodName ? methodName : ''; 205 | if (method) 206 | return str.replace(new RegExp(method + '\(.*\)', 'g'), (value) => { 207 | value = value.replace(new RegExp(regex, 'g'), (number) => { 208 | if (number == 0) return number; 209 | number = fix(number); 210 | if (methodName == '') return number; 211 | return `${methodName}(` + number + ')'; 212 | }); 213 | return value; 214 | }); 215 | return str.replace(new RegExp(tag + ': ' + regex, 'g'), (value) => { 216 | var matches_array = value.match(regex); 217 | if (matches_array[0] == 0) return value; 218 | matches_array[0] = fix(matches_array[0]); 219 | if (methodName == '') return tag + ': ' + matches_array[0]; 220 | return tag + `: ${methodName}(` + matches_array[0] + ')'; 221 | }); 222 | } 223 | 224 | function getParamList(arr) { 225 | let str = ''; 226 | arr.forEach((o) => { if (o) { str += o; } }); 227 | return str; 228 | } 229 | 230 | exports.getParamList = getParamList; -------------------------------------------------------------------------------- /src/items_to_dart.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Adobe 3 | All Rights Reserved. 4 | NOTICE: Adobe permits you to use, modify, and distribute this file in 5 | accordance with the terms of the Adobe license agreement accompanying 6 | it. If you have received this file from a source other than Adobe, 7 | then your use, modification, or distribution of it requires the prior 8 | written permission of Adobe. 9 | */ 10 | 11 | const { xdItemToWidget, widgetCanHaveChild, removeItemsFromGroup, applyRegex } = require("./util"); 12 | const { Bounds } = require("./bounds"); 13 | const { Children } = require("./widgets/children"); 14 | const { ArtboardWidget } = require("./widgets/artboard"); 15 | const { wrapWithInkWell, wrapWithRotation } = require("./widgets/util/widgets_util"); 16 | const { ComponentWidget } = require("./widgets/component"); 17 | const { formatDart } = require("./widgets/util/format_dart"); 18 | 19 | function itemsToDart(items, isFirst = false) { 20 | const ungroupedItems = removeItemsFromGroup(items); 21 | const widgets = generateWidgetsFromItems(ungroupedItems); 22 | const tree = new Tree(widgets, isFirst); 23 | return tree.toDart(); 24 | } 25 | 26 | exports.itemsToDart = itemsToDart; 27 | 28 | function generateWidgetsFromItems(items) { 29 | const widgets = []; 30 | items.forEach(item => { 31 | widgets.push(xdItemToWidget(item)); 32 | }); 33 | return widgets; 34 | } 35 | 36 | 37 | class Tree { 38 | /** 39 | * @param {[any]} widgets list of all selection widgets 40 | */ 41 | constructor(widgets, isFirst) { 42 | this.node = new Node(widgets[0]); 43 | for (let i = 1; i < widgets.length; i++) { 44 | const widget = widgets[i]; 45 | this.node = this.insertNodeIn(new Node(widget), this.node); 46 | } 47 | this.isFirst = isFirst; 48 | // console.log(this.node.debug(0)); 49 | } 50 | 51 | toDart() { 52 | let widget = this.node.toDart(); 53 | if (this.isFirst) { 54 | widget = formatDart(widget + ';'); 55 | return applyRegex(widget); 56 | } 57 | return widget; 58 | } 59 | 60 | 61 | /** 62 | * Insert new Node to the Tree 63 | * 64 | * @param {Node} newNode Node to be inserted 65 | * @param {Node} inNode Actual Tree's Node 66 | * @return {Node} inNode or new Tree head 67 | * 68 | * this function check relation between nodes 69 | * and insert the new Node in the tree 70 | */ 71 | insertNodeIn(newNode, inNode) { 72 | inNode.updateBounds(); 73 | const nodesRelation = this.relation(newNode, inNode); 74 | const canInside = widgetCanHaveChild(inNode.widget); 75 | const inNodeIsArtboard = inNode.widget instanceof ArtboardWidget; 76 | const inNodeChildIsStack = inNode.children.length > 0 && inNode.children[0].type == 'Stack'; 77 | if ((nodesRelation == 'inside' && canInside && (!inNodeIsArtboard || !inNodeChildIsStack)) || (nodesRelation == 'above' && (inNode.type == 'Row' || inNode.type == 'Column'))) { 78 | return this.insertInside(newNode, inNode); 79 | } else if (nodesRelation == 'inside' && inNodeIsArtboard && inNodeChildIsStack) { 80 | const firstChild = inNode.children[0].children[0]; 81 | const firstChildRelation = this.relation(firstChild, inNode); 82 | if (firstChildRelation == "above") { 83 | newNode.father = inNode.children[0]; 84 | inNode.children[0].children.splice(0, 0, newNode); 85 | } else { 86 | inNode.children[0].children[0] = this.insertNodeIn(newNode, inNode.children[0].children[0]); 87 | } 88 | return inNode; 89 | } else if (nodesRelation == 'above' || (!canInside && nodesRelation == 'inside')) { 90 | if (inNodeIsArtboard) { 91 | return this.insertNodeStackInArtboard(newNode, inNode); 92 | } 93 | return this.wrapNodesWithType([inNode, newNode], 'Stack'); 94 | } else if (nodesRelation == 'outside') { 95 | const better = this.betterOutside(newNode, inNode); 96 | return this.wrapNodesWithType([inNode, newNode], better); 97 | } 98 | return inNode; 99 | } 100 | 101 | insertNodeStackInArtboard(newNode, inNode) { 102 | if (inNode.children.length == 0) { 103 | const stackNode = new Node(null, inNode, 'Stack'); 104 | stackNode.bounds = inNode.bounds; 105 | inNode.children.push(stackNode); 106 | } 107 | const child = inNode.children[0]; 108 | if (child.widget == null && child.type == "Stack") { 109 | child.children.push(newNode); 110 | newNode.father = child; 111 | } else { 112 | const stackNode = new Node(null, inNode, 'Stack'); 113 | stackNode.bounds = inNode.bounds; 114 | child.father = stackNode; 115 | newNode.father = stackNode; 116 | stackNode.children.push(child); 117 | stackNode.children.push(newNode); 118 | inNode.children[0] = stackNode; 119 | } 120 | return inNode; 121 | } 122 | 123 | /** 124 | * Insert new Node inside Node 125 | * 126 | * @param {Node} newNode Node to be inserted 127 | * @param {Node} inNode Actual Tree's Node 128 | */ 129 | insertInside(newNode, inNode) { 130 | if (inNode.children.length == 0) { 131 | inNode.children.push(newNode); 132 | newNode.father = inNode; 133 | inNode.updateBounds(); 134 | return inNode; 135 | } else { 136 | const invertedType = inNode.type == `Row` ? `Column` : inNode.type == `Column` ? `Row` : ``; 137 | let insertPosition; 138 | let qtdAboves = 0; 139 | for (let i = 0; i < inNode.children.length; i++) { 140 | const child = inNode.children[i]; 141 | const nodesRelation = this.relation(newNode, child); 142 | if (nodesRelation == 'inside' || nodesRelation == 'above' || this.betterOutside(newNode, child) == invertedType) { 143 | if (nodesRelation == 'above') { 144 | qtdAboves++; 145 | } 146 | insertPosition = i; 147 | } 148 | } 149 | if (qtdAboves > 1) { 150 | return this.wrapNodesWithType([inNode, newNode], `Stack`); 151 | } else if (insertPosition != null && qtdAboves < 2) { 152 | inNode.children[insertPosition] = this.insertNodeIn(newNode, inNode.children[insertPosition]); 153 | inNode.updateBounds(); 154 | return inNode; 155 | } 156 | inNode.children[0] = this.insertNodeIn(newNode, inNode.children[0]); 157 | inNode.updateBounds(); 158 | return inNode; 159 | } 160 | } 161 | 162 | /** 163 | * Check relation between two nodes 164 | * 165 | * @param {Node} node1 The First Node 166 | * @param {Node} node2 The Second Node 167 | * @returns {string} (inside, outside or above); 168 | */ 169 | relation(newNode, inNode) { 170 | const node1Bounds = inNode.bounds; 171 | const node2Bounds = newNode.bounds; 172 | const boundsX1 = node1Bounds.x1 <= node2Bounds.x1 ? node1Bounds : node2Bounds; 173 | const boundsX2 = node1Bounds.x1 <= node2Bounds.x1 ? node2Bounds : node1Bounds; 174 | const boundsY1 = node1Bounds.y1 <= node2Bounds.y1 ? node1Bounds : node2Bounds; 175 | const boundsY2 = node1Bounds.y1 <= node2Bounds.y1 ? node2Bounds : node1Bounds; 176 | const canBeInsideX = boundsX1.x2 >= boundsX2.x2; 177 | const canBeInsideY = boundsY1.y2 >= boundsY2.y2; 178 | if (canBeInsideX && canBeInsideY && boundsX1 == boundsY1) return 'inside'; 179 | const insideX = boundsX1.x2 > boundsX2.x1; 180 | const insideY = boundsY1.y2 > boundsY2.y1; 181 | if ((canBeInsideY && insideX) || (canBeInsideX && insideY) || (insideX && insideY)) return 'above'; 182 | return 'outside'; 183 | } 184 | 185 | /** 186 | * Wrap node with children node type 187 | * 188 | * @param {[Node]} nodes AllNodes 189 | * @param {string} wrapperType Wrapper type (Column, Row or Stack) 190 | * @return {Node} wrapperNo, ex: oldNode = wrapNodesWithType(...); 191 | */ 192 | wrapNodesWithType(nodes, wrapperType) { 193 | const first = nodes[0]; 194 | const father = first.father; 195 | const fatherType = father != null ? father.type : null; 196 | if (first.type == wrapperType) { 197 | first.children.push(nodes[1]); 198 | nodes[1].father = first; 199 | first.children = this.sortNodesByType(wrapperType, first.children); 200 | first.updateBounds(); 201 | return first; 202 | } else if (fatherType == wrapperType) { 203 | father.children.push(nodes[1]); 204 | nodes[1].father = first; 205 | father.children = this.sortNodesByType(wrapperType, father.children); 206 | father.updateBounds(); 207 | return first; 208 | } 209 | const wrapperNo = new Node(null, father, wrapperType); 210 | nodes.forEach(node => { 211 | node.father = wrapperNo; 212 | wrapperNo.children.push(node); 213 | }); 214 | wrapperNo.children = this.sortNodesByType(wrapperType, wrapperNo.children); 215 | wrapperNo.updateBounds(); 216 | return wrapperNo; 217 | } 218 | 219 | /** 220 | * @param {string} sortType (Row, Column or Stack) 221 | * @param {[Node]} nodes nodes to be ordered 222 | * @returns {[Node]} ordered nodes 223 | */ 224 | sortNodesByType(sortType, nodes) { 225 | if (sortType == 'Row') { 226 | nodes = nodes.sort((a, b) => a.bounds.x1 - b.bounds.x1); 227 | } else if (sortType == 'Column') { 228 | nodes = nodes.sort((a, b) => a.bounds.y1 - b.bounds.y1); 229 | } 230 | return nodes; 231 | } 232 | 233 | /** 234 | * Select better between Row and Column 235 | * 236 | * @param {Node} newNode 237 | * @param {Node} inNode 238 | * @returns {string} better type (Row or Column) 239 | */ 240 | betterOutside(newNode, inNode) { 241 | let node1Bounds = newNode.bounds; 242 | let node2Bounds = inNode.bounds; 243 | let bounds1 = node1Bounds.x1 <= node2Bounds.x1 ? node1Bounds : node2Bounds; 244 | let bounds2 = node1Bounds.x1 <= node2Bounds.x1 ? node2Bounds : node1Bounds; 245 | let xDistance = bounds2.x1 - bounds1.x2; 246 | bounds1 = node1Bounds.y1 <= node2Bounds.y1 ? node1Bounds : node2Bounds; 247 | bounds2 = node1Bounds.y1 <= node2Bounds.y1 ? node2Bounds : node1Bounds; 248 | let yDistance = bounds2.y1 - bounds1.y2; 249 | if (xDistance < 0) return `Column`; 250 | if (yDistance < 0) return `Row`; 251 | return xDistance < yDistance ? 'Column' : 'Row'; 252 | } 253 | 254 | } 255 | 256 | class Node { 257 | /** 258 | * @param {[any]} widgets list of all selection widgets 259 | * @param {Node} father Father Node 260 | * @param {string} type (Column, Row or Stack) 261 | */ 262 | constructor(widget, father, type) { 263 | this.widget = widget; 264 | this.father = father; 265 | this.type = type == null ? widget.xdNode.name : type; 266 | this.children = []; 267 | this.bounds; 268 | this.updateBounds(); 269 | } 270 | 271 | /** 272 | * @return {Bounds} Node bounds 273 | */ 274 | updateBounds() { 275 | if (this.widget != null) { 276 | const widgetBounds = this.widget.bounds; 277 | this.bounds = new Bounds(null, widgetBounds); 278 | } else { 279 | if (this.children.length > 0) { 280 | const widgetBounds = this.children[0].bounds; 281 | const thisBounds = new Bounds(null, widgetBounds); 282 | this.children.forEach(child => { 283 | thisBounds.x1 = Math.min(thisBounds.x1, child.bounds.x1); 284 | thisBounds.x2 = Math.max(thisBounds.x2, child.bounds.x2); 285 | thisBounds.y1 = Math.min(thisBounds.y1, child.bounds.y1); 286 | thisBounds.y2 = Math.max(thisBounds.y2, child.bounds.y2); 287 | }); 288 | this.bounds = thisBounds; 289 | } 290 | } 291 | } 292 | 293 | /** 294 | * @param {number} depth depth in the Tree to indent the code 295 | * @return {string} Generated dart code 296 | */ 297 | toDart() { 298 | if (this.widget == null) { 299 | this.widget = new Children(this.type, this); 300 | } 301 | const child = this.children != null ? this.children[0] : null 302 | const dartWidget = this.widget.toDart(child); 303 | if (this.widget instanceof ComponentWidget) { 304 | return dartWidget; 305 | } 306 | return wrapWithRotation(this, wrapWithInkWell(this.widget.xdNode, dartWidget)); 307 | } 308 | 309 | debug(depth) { 310 | const children = []; 311 | this.children.forEach(child => { 312 | children.push(`\n${child.debug(depth + 1)}`); 313 | }); 314 | let tabs = `| `; 315 | for (let i = 0; i < depth; i++) { 316 | tabs += '| '; 317 | } 318 | return `${tabs}${this.type}, h = ${this.bounds.y2 - this.bounds.y1}, w = ${this.bounds.x2 - this.bounds.x1} ${children}`; 319 | } 320 | 321 | isChildren() { 322 | return this.widget == null || this.widget instanceof Children; 323 | } 324 | } 325 | exports.Node = Node; -------------------------------------------------------------------------------- /src/widgets/svg.js: -------------------------------------------------------------------------------- 1 | const { Bounds } = require("../bounds"); 2 | const xd = require("scenegraph"); 3 | 4 | class SvgWidget { 5 | constructor(xdNode) { 6 | this.shapes = []; 7 | this.xdNode = xdNode; 8 | this.bounds = new Bounds(xdNode); 9 | if (xdNode.constructor.name == 'Group') { 10 | this.addShapesFromGroup(xdNode); 11 | } else { 12 | this.shapes.push(xdNode); 13 | } 14 | } 15 | 16 | toDart() { 17 | const node = this.xdNode; 18 | if (node.name.includes('svg_')) return this.assetSvg(); 19 | const path = new Path(node); 20 | path.shapes = this.shapes; 21 | return path.toString(); 22 | } 23 | 24 | assetSvg() { 25 | const node = this.xdNode; 26 | const { fix } = require("../util"); 27 | let height = node.height; 28 | height = height != null ? height : node.localBounds.height; 29 | height = height == 0 ? 1 : height; 30 | let width = node.width; 31 | width = width != null ? width : node.localBounds.width; 32 | width = width == 0 ? 1 : width; 33 | let withStyledWidget = document.querySelector('input[name="simpleType"]'); 34 | withStyledWidget = withStyledWidget != null ? withStyledWidget.checked : null; 35 | if (withStyledWidget) { 36 | return ` 37 | //TODO: ${node.name} 38 | 'assets/${node.name.replace('svg_', '')}.svg'.svgAsset().w(${fix(width)}).h(${fix(height)})`; 39 | } 40 | return `SvgPicture.asset( 41 | //TODO: ${node.name} 42 | 'assets/${node.name.replace('svg_', '')}.svg', 43 | width: ${width}, 44 | height: ${height}, 45 | )`; 46 | } 47 | 48 | addShapesFromGroup(xdNode) { 49 | xdNode.children.forEach(child => { 50 | if (child.constructor.name == 'Group') { 51 | this.addShapesFromGroup(child); 52 | } else { 53 | this.shapes.push(child); 54 | } 55 | }); 56 | } 57 | } 58 | 59 | exports.SvgWidget = SvgWidget; 60 | 61 | class Path { 62 | constructor(xdNode) { 63 | this.xdNode = xdNode; 64 | this.shapes = []; 65 | this.viewBox = null; 66 | } 67 | 68 | calculateViewBox() { 69 | if (this.viewBox) 70 | return; 71 | 72 | this.viewBox = _calculateAggregateViewBox(this.shapes); 73 | } 74 | 75 | get boundsInParent() { 76 | this.calculateViewBox(); 77 | return this.xdNode.transform.transformRect(this.viewBox); 78 | } 79 | 80 | adjustTransform() { 81 | this.calculateViewBox(); 82 | return new xd.Matrix(1.0, 0.0, 0.0, 1.0, this.viewBox.x, this.viewBox.y); 83 | } 84 | 85 | toString() { 86 | let svg; 87 | const { fix } = require("../util"); 88 | svg = `'${this.toSvgString()}'`; 89 | const node = this.xdNode; 90 | let height = node.height; 91 | height = height != null ? height : node.localBounds.height; 92 | height = height == 0 ? 1 : height; 93 | let width = node.width; 94 | width = width != null ? width : node.localBounds.width; 95 | width = width == 0 ? 1 : width; 96 | let withStyledWidget = document.querySelector('input[name="simpleType"]'); 97 | withStyledWidget = withStyledWidget != null ? withStyledWidget.checked : null; 98 | if (withStyledWidget) { 99 | return ` 100 | // ${this.xdNode.name} 101 | ${svg}.svgString().w(${fix(width)}).h(${fix(height)})`; 102 | } 103 | return `SvgPicture.string( 104 | // ${this.xdNode.name} 105 | ${svg}, 106 | width: ${width}, 107 | height: ${height}, 108 | )`; 109 | } 110 | 111 | toSvgString() { 112 | this.calculateViewBox(); 113 | const { fix } = require("../util"); 114 | let vx = fix(this.viewBox.x); 115 | let vy = fix(this.viewBox.y); 116 | // For some reason xd can have a viewport with 0 extent so clamp it to 1 117 | let vw = fix(Math.max(this.viewBox.width, 1)); 118 | let vh = fix(Math.max(this.viewBox.height, 1)); 119 | 120 | let svg = ""; 121 | for (let i = 0; i < this.shapes.length; ++i) { 122 | let o = this.shapes[i]; 123 | if (o instanceof Path) { 124 | svg += _serializeSvgGroup(o); 125 | } else { 126 | svg += _serializeSvgShape(o); 127 | } 128 | } 129 | svg = `${svg}`; 130 | 131 | return svg; 132 | } 133 | } 134 | 135 | exports.Path = Path; 136 | 137 | function _serializeSvgGroup(node) { 138 | let result = ""; 139 | let xform = _getSvgTransform(node.xdNode.transform); 140 | result += ``; 141 | for (let i = 0; i < node.shapes.length; ++i) { 142 | let o = node.shapes[i]; 143 | if (o instanceof Path) { 144 | result += _serializeSvgGroup(o); 145 | } else { 146 | result += _serializeSvgShape(o); 147 | } 148 | } 149 | result += ""; 150 | return result; 151 | } 152 | 153 | function _serializeSvgShape(o) { 154 | let pathStr = o.pathData; 155 | const { getOpacity } = require("../util"); 156 | let opacity = getOpacity(o); 157 | let fill = "none"; 158 | let fillOpacity = opacity; 159 | let hasImageFill = false; 160 | let hasGradientFill = false; 161 | if (o.fill && o.fillEnabled) { 162 | hasImageFill = (o.fill instanceof xd.ImageFill); 163 | hasGradientFill = (o.fill instanceof xd.LinearGradient) || (o.fill instanceof xd.RadialGradient); 164 | if (hasImageFill) { 165 | fill = "url(#image)"; 166 | } else if (hasGradientFill) { 167 | fill = "url(#gradient)"; 168 | } else { 169 | fill = "#" + getRGBHex(o.fill); 170 | fillOpacity = (o.fill.a / 255.0) * opacity; 171 | } 172 | } 173 | if (hasImageFill) { 174 | throw 'Image fills are not supported on shapes.'; 175 | } 176 | const { fix } = require("../util"); 177 | let imagePath = hasImageFill ? getImagePath(o) : ""; 178 | let imageWidth = fix(hasImageFill ? o.fill.naturalWidth : 0); 179 | let imageHeight = fix(hasImageFill ? o.fill.naturalHeight : 0); 180 | let stroke = (o.stroke && o.strokeEnabled) ? "#" + getRGBHex(o.stroke) : "none"; 181 | let strokeOpacity = (o.stroke && o.strokeEnabled) ? (o.stroke.a / 255.0) * opacity : opacity; 182 | let strokeWidth = o.strokeWidth; 183 | let strokeDash = o.strokeDashArray.length > 0 ? o.strokeDashArray[0] : 0; 184 | let strokeGap = o.strokeDashArray.length > 1 ? o.strokeDashArray[1] : strokeDash; 185 | let strokeOffset = o.strokeDashArray.length > 0 ? o.strokeDashOffset : 0; 186 | let strokeMiterLimit = o.strokeJoins == xd.GraphicNode.STROKE_JOIN_MITER 187 | ? o.strokeMiterLimit : 0; 188 | let strokeCap = o.strokeEndCaps; 189 | let strokeJoin = o.strokeJoins; 190 | 191 | let fillAttrib = `fill="${fill}"`; 192 | if (fillOpacity != 1.0) 193 | fillAttrib += ` fill-opacity="${fix(fillOpacity, 2)}"`; 194 | let strokeAttrib = `stroke="${stroke}" stroke-width="${strokeWidth}"`; 195 | 196 | if (strokeOpacity != 1.0) 197 | strokeAttrib += ` stroke-opacity="${fix(strokeOpacity, 2)}"`; 198 | if (strokeGap != 0) 199 | strokeAttrib += ` stroke-dasharray="${strokeDash} ${strokeGap}"`; 200 | if (strokeOffset != 0) 201 | strokeAttrib += ` stroke-dashoffset="${strokeOffset}"`; 202 | if (strokeMiterLimit != 0) 203 | strokeAttrib += ` stroke-miterlimit="${strokeMiterLimit}"`; 204 | if (strokeCap != xd.GraphicNode.STROKE_CAP_BUTT) 205 | strokeAttrib += ` stroke-linecap="${strokeCap}"`; 206 | if (strokeJoin != xd.GraphicNode.STROKE_JOIN_MITER) 207 | strokeAttrib += ` stroke-linejoin="${strokeJoin}"`; 208 | 209 | let hasShadow = o.shadow && o.shadow.visible; 210 | if (hasShadow) { 211 | throw 'Shadows are not supported on shapes.'; 212 | } 213 | let filterAttrib = hasShadow ? `filter="url(#shadow)"` : ``; 214 | let shadowOffsetX = hasShadow ? o.shadow.x : 0; 215 | let shadowOffsetY = hasShadow ? o.shadow.y : 0; 216 | let shadowBlur = hasShadow ? o.shadow.blur : 0; 217 | 218 | let defs = ""; 219 | if (hasShadow) { 220 | defs += ``; 221 | } 222 | if (hasImageFill) { 223 | defs += ``; 224 | } 225 | if (hasGradientFill) { 226 | if (o.fill instanceof xd.LinearGradient) { 227 | const x1 = fix(o.fill.startX, 6); 228 | const y1 = fix(o.fill.startY, 6); 229 | const x2 = fix(o.fill.endX, 6); 230 | const y2 = fix(o.fill.endY, 6); 231 | defs += ``; 232 | for (let stop of o.fill.colorStops) { 233 | const offset = fix(stop.stop, 6); 234 | const color = getARGBHexWithOpacity(stop.color); 235 | const opacity = stop.color.a !== 255 ? `stop-opacity="${fix(stop.color.a / 255.0, 2)}"` : ""; 236 | defs += ``; 237 | } 238 | defs += ``; 239 | } else if (o.fill instanceof xd.RadialGradient) { 240 | const inv = o.fill.gradientTransform.invert(); 241 | const start = inv.transformPoint({ x: o.fill.startX, y: o.fill.startY }); 242 | const end = inv.transformPoint({ x: o.fill.endX, y: o.fill.endY }); 243 | const fx = fix(start.x, 6); 244 | const fy = fix(start.y, 6); 245 | const fr = fix(o.fill.startR, 6); 246 | const cx = fix(end.x, 6); 247 | const cy = fix(end.y, 6); 248 | const r = fix(o.fill.endR, 6); 249 | const a = fix(o.fill.gradientTransform.a, 6); 250 | const b = fix(o.fill.gradientTransform.b, 6); 251 | const c = fix(o.fill.gradientTransform.c, 6); 252 | const d = fix(o.fill.gradientTransform.d, 6); 253 | const e = fix(o.fill.gradientTransform.e, 6); 254 | const f = fix(o.fill.gradientTransform.f, 6); 255 | let xform = ""; 256 | if (a !== 1.0 || b !== 0.0 || c !== 0.0 || d !== 1.0 || e !== 0.0 || f !== 0.0) { 257 | xform = `gradientTransform="matrix(${a} ${b} ${c} ${d} ${e} ${f})"`; 258 | } 259 | defs += ``; 260 | for (let stop of o.fill.colorStops) { 261 | const offset = fix(stop.stop, 6); 262 | const color = getRGBHex(stop.color); 263 | const opacity = stop.color.a !== 255 ? `stop-opacity="${fix(stop.color.a / 255.0, 2)}"` : ""; 264 | defs += ``; 265 | } 266 | defs += ``; 267 | } 268 | } 269 | defs = defs ? `${defs}` : ""; 270 | 271 | o.transform.translate(o.localBounds.x, o.localBounds.y); 272 | const xform = _getSvgTransform(o.transform); 273 | let transformAttrib = xform ? `transform="${xform}"` : ""; 274 | 275 | let str = `${defs}`; 276 | return str; 277 | } 278 | 279 | function _getSvgTransform(transform) { 280 | let result; 281 | const { fix } = require("../util"); 282 | if (transform.a !== 1.0 || transform.b !== 0.0 || transform.c !== 0.0 || transform.d !== 1.0) { 283 | // Use full transform 284 | const a = fix(transform.a, 6); 285 | const b = fix(transform.b, 6); 286 | const c = fix(transform.c, 6); 287 | const d = fix(transform.d, 6); 288 | const e = fix(transform.e, 2); 289 | const f = fix(transform.f, 2); 290 | result = `matrix(${a}, ${b}, ${c}, ${d}, ${e}, ${f})`; 291 | } else if (transform.e !== 0.0 || transform.f !== 0.0) { 292 | // Use offset transform 293 | const e = fix(transform.e, 2); 294 | const f = fix(transform.f, 2); 295 | result = `translate(${e}, ${f})`; 296 | } else { 297 | result = ""; 298 | } 299 | return result; 300 | } 301 | 302 | function _calculateAggregateViewBox(shapes) { 303 | let minX = Number.MAX_VALUE; 304 | let minY = Number.MAX_VALUE; 305 | let maxX = -Number.MAX_VALUE; 306 | let maxY = -Number.MAX_VALUE; 307 | 308 | for (let o of shapes) { 309 | if (o.boundsInParent.x < minX) 310 | minX = o.boundsInParent.x; 311 | if (o.boundsInParent.y < minY) 312 | minY = o.boundsInParent.y; 313 | if (o.boundsInParent.x + o.boundsInParent.width > maxX) 314 | maxX = o.boundsInParent.x + o.boundsInParent.width; 315 | if (o.boundsInParent.y + o.boundsInParent.height > maxY) 316 | maxY = o.boundsInParent.y + o.boundsInParent.height; 317 | } 318 | 319 | return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }; 320 | } 321 | 322 | 323 | function getRGBHex(color) { 324 | return getColorComponent(color.r) + 325 | getColorComponent(color.g) + 326 | getColorComponent(color.b); 327 | } 328 | function getColorComponent(val) { 329 | return (val < 0x10 ? "0" : "") + Math.round(val).toString(16); 330 | } 331 | 332 | function getARGBHexWithOpacity(color, opacity = 1) { 333 | return getColorComponent(color.a * opacity) + 334 | getColorComponent(color.r) + 335 | getColorComponent(color.g) + 336 | getColorComponent(color.b); 337 | } -------------------------------------------------------------------------------- /src/widgets/text.js: -------------------------------------------------------------------------------- 1 | const { Bounds } = require("../bounds"); 2 | const { Parameter, ParameterRef } = require("./util/parameter"); 3 | const { getColor } = require("./util/color"); 4 | const { titleCase } = require("./util/widgets_util"); 5 | const { googleFonts } = require("./util/google_fonts"); 6 | const { changeOutputUiText } = require("../ui/components/output_ui"); 7 | 8 | class TextWidget { 9 | constructor(xdNode) { 10 | this.xdNode = xdNode; 11 | this.bounds = new Bounds(xdNode); 12 | this.parameters = {}; 13 | 14 | let textParam = new Parameter(this, "String", "text", xdNode.text); 15 | this.parameters["text"] = new ParameterRef(textParam, true, 'teste2'); 16 | 17 | let colorParam = new Parameter(this, "Color", "fill", xdNode.fill); 18 | this.parameters["fill"] = new ParameterRef(colorParam, true, 'teste'); 19 | } 20 | 21 | toDart() { 22 | let str, o = this.xdNode, params = this.parameters; 23 | 24 | checkForUnsupportedFeatures(o); 25 | 26 | if (o.styleRanges.length > 1) { 27 | str = _getTextRich(o, params); 28 | } else { 29 | str = _getText(o, params); 30 | } 31 | if (o.areaBox) { 32 | let w = o.localBounds.width; 33 | let h = o.localBounds.height; 34 | let withStyledWidget = document.querySelector('input[name="simpleType"]'); 35 | withStyledWidget = withStyledWidget != null ? withStyledWidget.checked : null; 36 | if (withStyledWidget) { 37 | str = `${str}.w(${w}).h(${h})` 38 | } else { 39 | str = `SizedBox( 40 | width: ${w}, 41 | height: ${h}, 42 | child: ${str}, 43 | )` 44 | } 45 | } 46 | return str; 47 | } 48 | } 49 | 50 | exports.TextWidget = TextWidget; 51 | 52 | function checkForUnsupportedFeatures(o) { 53 | if (o.textScript !== 'none') { 54 | changeOutputUiText('Superscript & subscript are not currently supported.', 'Brown'); 55 | } 56 | if (o.paragraphSpacing) { 57 | changeOutputUiText('Paragraph spacing is not currently supported.', 'Brown'); 58 | } 59 | // if (o.strokeEnabled && o.stroke) { 60 | // changeOutputUiText('Text border is not currently supported.', 'Brown'); 61 | // } 62 | } 63 | 64 | function _textTransformation(text, xdNode) { 65 | if (xdNode.textTransform == 'none') return text; 66 | if (xdNode.textTransform == 'uppercase') return text.toUpperCase().split('\\N').join('\\n'); 67 | if (xdNode.textTransform == 'lowercase') return text.toLowerCase(); 68 | if (xdNode.textTransform == 'titlecase') return titleCase(text); 69 | throw xdNode.textTransform; 70 | } 71 | 72 | function _getText(xdNode, params) { 73 | let textParam = params["text"].isOwn 74 | ? `'${escapeString(xdNode.text)}'` 75 | : params["text"].name; 76 | textParam = _textTransformation(textParam, xdNode); 77 | let withStyledWidget = document.querySelector('input[name="simpleType"]'); 78 | withStyledWidget = withStyledWidget != null ? withStyledWidget.checked : null; 79 | if (withStyledWidget) return styledText(xdNode, textParam); 80 | return _borderText(xdNode, 'Text(' 81 | + `${textParam},` + 82 | _getStyleParam(xdNode, _getTextStyleParamList(xdNode, null, params)) + 83 | _getHeightBehavior(xdNode) + 84 | _getTextAlignParam(xdNode) + 85 | ')'); 86 | } 87 | 88 | function _borderText(xdNode, dartCode) { 89 | const { getOpacity } = require("../util"); 90 | if (xdNode.strokeEnabled && xdNode.stroke) { 91 | const xdSec = xdNode.strokeEndCaps; 92 | const xdSj = xdNode.strokeJoins; 93 | const sc = xdSec == 'round' ? '' : `strokeCap: StrokeCap.${xdSec},`; 94 | const sj = xdSj == 'round' ? '' : `strokeJoin: StrokeJoin.${xdSj},`; 95 | const sw = xdNode.strokeWidth == 6 ? '' : `strokeWidth: ${xdNode.strokeWidth},`; 96 | return ` 97 | BorderedText( //TODO: install bordered_text package 98 | ${sw} 99 | strokeColor: ${getColor(xdNode.stroke, getOpacity(xdNode))}, 100 | ${sc} 101 | ${sj} 102 | child: ${dartCode}, 103 | )`; 104 | } 105 | return dartCode; 106 | } 107 | 108 | function styledText(xdNode, textParam) { 109 | //TODO: tAlignment 110 | const { getOpacity } = require("../util"); 111 | const al = _getStyledTextAlign(xdNode); 112 | const c = `.color(${getColor(xdNode.fill, getOpacity(xdNode))})`; 113 | const fs = `.size(${xdNode.fontSize})`; 114 | const family = _getFontFamilyName(xdNode); 115 | const wgf = googleFonts.includes(family); 116 | const weight = _getFontWeight(xdNode.fontStyle); 117 | const fw = !wgf && weight ? `.weight(${weight})` : ''; 118 | const gfw = wgf && weight ? `fontWeight: ${weight}` : ''; 119 | const ff = wgf ? `.textStyle(GoogleFonts.${family}(${gfw}))` : `.family('${_getFontFamily(xdNode)}')`; 120 | //! Text Shadow 121 | const shadow = xdNode.shadow; 122 | let ts = ''; 123 | const withShadow = shadow != null && shadow.visible; 124 | if (withShadow) { 125 | const blur = shadow.blur ? shadow.blur != 0 ? `blurRadius: ${shadow.blur},` : '' : ''; 126 | const color = `color: ${getColor(shadow.color)}`; 127 | const x = shadow.x; 128 | const y = shadow.y; 129 | const offset = (x || y ? x == 0 && y == 0 ? '' : `, offset: Offset(${x}, ${y}),` : ''); 130 | ts = `.shadow(${blur}${color}${offset})`; 131 | } 132 | //! Text Decoration 133 | let td = ''; 134 | if (xdNode.underline || xdNode.strikethrough) { 135 | let u = xdNode.underline, s = xdNode.strikethrough; 136 | if (u && s) { 137 | td = '.combine([TextDecoration.underline, TextDecoration.lineThrough])'; 138 | } else { 139 | td = u ? '.underline' : '.lineThrough' 140 | } 141 | } 142 | //! Text Border 143 | let tb = ''; 144 | if (xdNode.strokeEnabled && xdNode.stroke) { 145 | const xdSec = xdNode.strokeEndCaps; 146 | const xdSj = xdNode.strokeJoins; 147 | const sc = xdSec == 'round' ? '' : `cap: StrokeCap.${xdSec},`; 148 | const sj = xdSj == 'round' ? '' : `join: StrokeJoin.${xdSj},`; 149 | const sw = xdNode.strokeWidth == 6 ? '' : `width: ${xdNode.strokeWidth},`; 150 | tb = `.border(color:${getColor(xdNode.stroke, getOpacity(xdNode))},${sw}${sc}${sj})`; 151 | } 152 | return `${textParam}.text()${ff}${c}${al}${fs}${fw}${ts}${td}${tb}`; 153 | } 154 | 155 | function escapeString(str) { 156 | return str.replace(/(['\\$])/g, '\\$1') // escaped characters 157 | .replace(/\n/g, '\\n'); // line breaks 158 | } 159 | 160 | function _getTextRich(xdNode, params) { 161 | let text = xdNode.text; 162 | let styles = xdNode.styleRanges; 163 | let str = '', j = 0; 164 | let defaultStyleParams = _getTextStyleParamList(xdNode, styles[0], params, true); 165 | 166 | for (let i = 0; i < styles.length; i++) { 167 | let style = styles[i], l = style.length; 168 | if (style.length === 0) { continue; } 169 | let styleParams = _getTextStyleParamList(xdNode, styles[i], params); 170 | let delta = getParamDelta(defaultStyleParams, styleParams); 171 | if (i === styles.length - 1) { l = text.length - j; } // for some reason, XD doesn't always return the correct length for the last entry. 172 | str += _getTextSpan(delta, text.substr(j, l), xdNode) + ', '; 173 | j += l; 174 | } 175 | 176 | // Export a rich text object with an empty root span setting a default style. 177 | // Child spans set their style as a delta of the default. 178 | return _borderText(xdNode, 'Text.rich(TextSpan(' + 179 | ' ' + _getStyleParam(xdNode, defaultStyleParams) + 180 | ` children: [${str}],` + _getHeightBehavior(xdNode) + 181 | `), ${_getTextAlignParam(xdNode)})`); 182 | 183 | } 184 | 185 | function _getTextSpan(params, text, xdNode) { 186 | text = escapeString(text); 187 | text = _textTransformation(text, xdNode); 188 | return 'TextSpan(' + 189 | ` text: '${text}',` + 190 | _getStyleParam(xdNode, params) + 191 | ')'; 192 | } 193 | 194 | function _getTextAlignParam(xdNode) { 195 | const align = _getTextAlign(xdNode.textAlign) 196 | if (align == 'TextAlign.left') return ''; 197 | return `textAlign: ${align}, `; 198 | } 199 | 200 | function _getTextStyleParamList(xdNode, styleRange, params, isDefault = false) { 201 | // Builds an array of style parameters. 202 | let o = styleRange || xdNode; 203 | return [ 204 | _getFontFamilyParam(o), 205 | _getFontSizeParam(o), 206 | _getColorParam(o, params), 207 | _getLetterSpacingParam(o), 208 | // The default style doesn't include weight, decoration, or style (italic): 209 | (isDefault ? null : _getFontStyleParam(o)), 210 | (isDefault ? null : _getFontWeightParam(o)), 211 | (isDefault ? null : _getTextDecorationParam(o)), 212 | // Line height & shadows are set at the node level in XD, so not included for ranges: 213 | (!styleRange || isDefault ? _getHeightParam(xdNode) : null), 214 | (!styleRange || isDefault ? _getShadowsParam(xdNode) : null), 215 | ]; 216 | } 217 | 218 | exports._getTextStyleParamList = _getTextStyleParamList; 219 | 220 | function _getStyleParam(xdNode, params, withTag = true) { 221 | if (!params) { return ''; } 222 | const { getParamList } = require("../util"); 223 | let str = getParamList(params); 224 | const family = _getFontFamilyName(xdNode); 225 | const tag = withTag ? 'style: ' : ''; 226 | const end = withTag ? ',' : ''; 227 | if (googleFonts.includes(family)) { 228 | return (!str ? '' : `${tag}GoogleFonts.${family}(${str})${end}`); 229 | } 230 | return (!str ? '' : `${tag}TextStyle(${str})${end} `); 231 | } 232 | 233 | exports._getStyleParam = _getStyleParam; 234 | 235 | function _getFontFamilyName(node) { 236 | let family = node.fontFamily.replace(/\s+/g, ''); 237 | family = family[0].toLowerCase() + family.substring(1, family.length); 238 | if (googleFonts.includes(family)) { 239 | return family; 240 | } 241 | return node.fontFamily; 242 | } 243 | 244 | function _getFontFamily(o) { 245 | return o.fontFamily; 246 | } 247 | 248 | function _getFontFamilyParam(o) { 249 | const family = _getFontFamilyName(o); 250 | if (googleFonts.includes(family)) return ''; 251 | return `fontFamily: '${_getFontFamily(o)}', `; 252 | } 253 | 254 | function _getFontSizeParam(o) { 255 | return `fontSize: ${o.fontSize}, `; 256 | } 257 | 258 | function _getColorParam(o, params) { 259 | const { getOpacity } = require("../util"); 260 | return `color: ${params["fill"].isOwn 261 | ? getColor(o.fill, getOpacity(o)) 262 | : params["fill"].name}, `; 263 | } 264 | 265 | function _getLetterSpacingParam(o) { 266 | // Flutter uses pixel values for letterSpacing. 267 | // XD uses increments of 1/1000 of the font size. 268 | return (o.charSpacing === 0 ? '' : 269 | `letterSpacing: ${o.charSpacing / 1000 * o.fontSize}, `); 270 | } 271 | 272 | function _getFontStyleParam(o) { 273 | let style = _getFontStyle(o.fontStyle); 274 | return style ? `fontStyle: ${style}, ` : ''; 275 | } 276 | 277 | function _getFontWeightParam(o) { 278 | let weight = _getFontWeight(o.fontStyle); 279 | return weight ? `fontWeight: ${weight}, ` : ''; 280 | } 281 | 282 | function _getTextDecorationParam(o) { 283 | let u = o.underline, s = o.strikethrough, str = ''; 284 | if (!u && !s) { return str; } 285 | if (u && s) { 286 | str = 'TextDecoration.combine([TextDecoration.underline, TextDecoration.lineThrough])'; 287 | } else { 288 | str = 'TextDecoration.' + (u ? 'underline' : 'lineThrough'); 289 | } 290 | return `decoration: ${str}, `; 291 | } 292 | 293 | function _getHeightParam(o) { 294 | // XD reports a lineSpacing of 0 to indicate default spacing. 295 | // Flutter uses a multiplier against the font size for its "height" value. 296 | // XD uses a pixel value. 297 | return (o.lineSpacing === 0 ? '' : 298 | `height: ${o.lineSpacing / o.fontSize}, `); 299 | } 300 | 301 | function _getHeightBehavior(o) { 302 | // XD reports a lineSpacing of 0 to indicate default spacing. 303 | // Flutter uses a multiplier against the font size for its "height" value. 304 | // XD uses a pixel value. 305 | return (o.lineSpacing === 0 ? '' : `textHeightBehavior: TextHeightBehavior(applyHeightToFirstAscent: false),`); 306 | } 307 | 308 | function _getShadowsParam(xdNode) { 309 | return ((xdNode.shadow == null || !xdNode.shadow.visible) ? '' : 310 | `shadows: [${_getShadow(xdNode.shadow)}], `); 311 | } 312 | 313 | function _getShadow(shadow) { 314 | let o = shadow; 315 | return `Shadow(color: ${getColor(o.color)}, ` + 316 | (o.x || o.y ? `offset: Offset(${o.x}, ${o.y}), ` : '') + 317 | (o.blur ? `blurRadius: ${o.blur}, ` : '') + '),'; 318 | } 319 | 320 | function _getTextAlign(align) { 321 | return 'TextAlign.' + (align == 'right' ? 'right' : 322 | align === 'center' ? 'center' : 'left'); 323 | } 324 | 325 | function _getStyledTextAlign(xdNode) { 326 | const align = xdNode.textAlign; 327 | if (align == 'left') return ''; 328 | if (align == 'right') return '.tRight'; 329 | if (align == 'center') return '.tCenter'; 330 | return ''; 331 | } 332 | 333 | function _getFontStyle(style) { 334 | style = style.toLowerCase(); 335 | let match = style.match(FONT_STYLES_RE); 336 | let val = match && FONT_STYLES[match]; 337 | return val ? 'FontStyle.' + val : null; 338 | } 339 | 340 | function _getFontWeight(style) { 341 | style = style.toLowerCase(); 342 | let match = style.match(FONT_WEIGHTS_RE); 343 | let val = match && FONT_WEIGHTS[match]; 344 | return val ? 'FontWeight.' + val : null; 345 | } 346 | 347 | function _buildStyleRegExp(map) { 348 | let list = []; 349 | for (let n in map) { list.push(n); } 350 | return new RegExp(list.join('|'), 'ig'); 351 | } 352 | 353 | // Used to translate font weight names from XD to Flutter constants: 354 | // https://www.webtype.com/info/articles/fonts-weights/ 355 | const FONT_WEIGHTS = { 356 | 'thin': 'w100', 357 | 'hairline': 'w100', 358 | 'extralight': 'w200', 359 | 'ultralight': 'w200', 360 | 'light': 'w300', 361 | 'book': 'w300', 362 | 'demi': 'w300', 363 | 364 | 'normal': null, // w400 365 | 'regular': null, 366 | 'plain': null, 367 | 368 | 'medium': 'w500', 369 | 'semibold': 'w600', 370 | 'demibold': 'w600', 371 | 'bold': 'w700', // or 'bold' 372 | 'extrabold': 'w800', 373 | 'heavy': 'w800', 374 | 'black': 'w900', 375 | 'poster': 'w900', 376 | } 377 | const FONT_WEIGHTS_RE = _buildStyleRegExp(FONT_WEIGHTS); 378 | 379 | const FONT_STYLES = { 380 | 'italic': 'italic', 381 | 'oblique': 'italic', 382 | } 383 | const FONT_STYLES_RE = _buildStyleRegExp(FONT_STYLES); 384 | 385 | function getParamDelta(defaultParams, params) { 386 | // Compares an array of params to an array of default params, 387 | // and returns a new array containing only the entries that differ, 388 | // or null if there is no difference. 389 | let delta = null; 390 | for (let i = 0; i < params.length; i++) { 391 | if (defaultParams[i] === params[i]) { continue; } 392 | if (delta === null) { delta = []; } 393 | delta.push(params[i]); 394 | } 395 | return delta; 396 | } 397 | -------------------------------------------------------------------------------- /src/widgets/children.js: -------------------------------------------------------------------------------- 1 | const { xdAlignmentToDartAlignment } = require('./util/xd_alignment_to_dart_alignment'); 2 | 3 | class Children { 4 | /** 5 | * @param {No} node 6 | */ 7 | constructor(type, node) { 8 | this.type = type; 9 | this.node = node; 10 | this.isStack = this.type == 'Stack'; 11 | this.stackAlignment = ''; 12 | this.columnOrRowMainAlignment = ''; 13 | this.columnOrRowCrossAlignment = ''; 14 | this.childrenSpaces = []; 15 | this.w; 16 | this.h; 17 | this.isStart = false; 18 | this.isEnd = false; 19 | } 20 | 21 | toDart() { 22 | const widgets = []; 23 | if (this.node.father != null) { 24 | if (this.type == 'Row') { 25 | this.node.bounds.x1 = this.node.father.bounds.x1; 26 | this.node.bounds.x2 = this.node.father.bounds.x2; 27 | } 28 | if (this.type == 'Column') { 29 | this.node.bounds.y1 = this.node.father.bounds.y1; 30 | this.node.bounds.y2 = this.node.father.bounds.y2; 31 | } 32 | } 33 | this.getPositionedDistances(); 34 | this.getChildrenSpaces(); 35 | let withStyledWidget = document.querySelector('input[name="simpleType"]'); 36 | withStyledWidget = withStyledWidget != null ? withStyledWidget.checked : null; 37 | this.buildWidgets(widgets, withStyledWidget); 38 | this.w = this.node.bounds.x2 - this.node.bounds.x1; 39 | this.h = this.node.bounds.y2 - this.node.bounds.y1; 40 | if (withStyledWidget) return this.simpleType(widgets); 41 | const width = `width: ${this.w},`; 42 | const height = `height: ${this.h},`; 43 | return ` 44 | SizedBox( 45 | ${width} 46 | ${height} 47 | child: ${this.type}( 48 | ${this.stackAlignment} 49 | ${this.columnOrRowMainAlignment} 50 | ${this.columnOrRowCrossAlignment} 51 | children: [${widgets},], 52 | ), 53 | )`; 54 | } 55 | 56 | simpleType(widgets) { 57 | const { fix } = require("../util"); 58 | return ` 59 | ${this.type}( 60 | ${this.stackAlignment} 61 | children: [${widgets},], 62 | )${this.columnOrRowMainAlignment}${this.columnOrRowCrossAlignment}.w(${fix(this.w)}).h(${fix(this.h)}) 63 | `; 64 | } 65 | 66 | buildWidgets(widgets, withStyledWidget) { 67 | if (this.isStack) this.getStackAlignment(); 68 | if (!this.isStack) this.getColumnOrRowMainAlignment(withStyledWidget); 69 | if (!this.isStack) this.getColumnOrRowCrossAlignment(withStyledWidget); 70 | this.node.children.forEach((child, index) => { 71 | if (this.isStack) { 72 | widgets.push(this.buildPositioneds(child, index, withStyledWidget)); 73 | } else { 74 | this.buildSpacer(widgets, index, withStyledWidget); 75 | widgets.push(this.buildAlignment(child, index)); 76 | } 77 | }); 78 | if (!this.isStack) { 79 | this.buildSpacer(widgets, this.childrenSpaces.length - 1, withStyledWidget); 80 | } 81 | } 82 | 83 | buildSpacer(widgets, index, withStyledWidget) { 84 | if (this.columnOrRowMainAlignment == '') { 85 | const spacer = this.childrenSpaces[index]; 86 | if (spacer > 0) { 87 | if (withStyledWidget) { 88 | widgets.push(`${spacer}.spacer`); 89 | } else { 90 | widgets.push(`Spacer(flex: ${spacer})`); 91 | } 92 | } 93 | } 94 | } 95 | 96 | getColumnOrRowMainAlignment(withSw) { 97 | const dist = this.childrenSpaces; 98 | let set = new Set(dist); 99 | if (set.size == 1) { 100 | if (set.getByIdx(0) == 0) return; 101 | this.columnOrRowMainAlignment = withSw ? '.mSpaceEvenly' : 'mainAxisAlignment: MainAxisAlignment.spaceEvenly,'; 102 | return; 103 | } 104 | set = new Set(dist.slice(1, dist.length - 1)); // remove first and last item of Set 105 | if (dist[0] == 0 && dist[dist.length - 1] == 0 && set.size == 1) { 106 | this.columnOrRowMainAlignment = withSw ? '.mSpaceBetween' : 'mainAxisAlignment: MainAxisAlignment.spaceBetween,'; 107 | return; 108 | } 109 | let first = dist[0]; 110 | if (first != 0 && dist[dist.length - 1] == first && set.size == 1 && set.getByIdx(0) == first * 2) { 111 | this.columnOrRowMainAlignment = withSw ? '.mSpaceAround' : 'mainAxisAlignment: MainAxisAlignment.spaceAround,'; 112 | return; 113 | } 114 | let last = dist[dist.length - 1]; 115 | if (first != 0 && last != 0 && first == last && set.size == 1 && set.getByIdx(0) == 0) { 116 | this.columnOrRowMainAlignment = withSw ? '.mCenter' : 'mainAxisAlignment: MainAxisAlignment.center,'; 117 | return; 118 | } 119 | set = new Set(dist.slice(1, dist.length)); // remove first and last item of Set 120 | if (first != 0 && set.size == 1 && set.getByIdx(0) == 0) { 121 | this.columnOrRowMainAlignment = withSw ? '.mEnd' : 'mainAxisAlignment: MainAxisAlignment.end,'; 122 | return; 123 | } 124 | } 125 | 126 | getColumnOrRowCrossAlignment(withSw) { 127 | let center = 0; 128 | let end = 0; 129 | let start = 0; 130 | for (let index = 0; index < this.topDistance.length; index++) { 131 | const top = this.topDistance[index]; 132 | const bot = this.botDistance[index]; 133 | const left = this.leftDistance[index]; 134 | const right = this.rightDistance[index]; 135 | const isColumn = this.type == 'Column'; 136 | 137 | let resAlignment = ''; 138 | if (isColumn) { 139 | if (left != right) { 140 | let auxRight = right == 0 && left == 0 ? 1 : right; 141 | const alignX = (left / (left + auxRight)); 142 | resAlignment = xdAlignmentToDartAlignment(alignX, 0.5); 143 | if (resAlignment == 'Alignment.centerLeft') { 144 | start++; 145 | } else if (resAlignment == 'Alignment.centerRight') { 146 | end++; 147 | } 148 | } else { 149 | center++; 150 | } 151 | } else { 152 | if (top != bot) { 153 | let auxBot = bot == 0 && top == 0 ? 1 : bot; 154 | const alignY = (top / (top + auxBot)); 155 | resAlignment = xdAlignmentToDartAlignment(0.5, alignY); 156 | if (resAlignment == 'Alignment.topCenter') { 157 | start++; 158 | } else if (resAlignment == 'Alignment.bottomCenter') { 159 | end++; 160 | } 161 | } else { 162 | center++; 163 | } 164 | } 165 | } 166 | if (center >= end && center >= start) return; 167 | if (end >= center && end >= start) { 168 | this.isEnd = true; 169 | this.columnOrRowCrossAlignment = withSw ? '.cEnd' : 'crossAxisAlignment: CrossAxisAlignment.end,'; 170 | } 171 | if (start >= center && start >= end) { 172 | this.isStart = true; 173 | this.columnOrRowCrossAlignment = withSw ? '.cStart' : 'crossAxisAlignment: CrossAxisAlignment.start,'; 174 | } 175 | } 176 | 177 | getChildrenSpaces() { 178 | const isColumn = this.type == 'Column'; 179 | if (!this.isStack) { 180 | let antChildEnd = isColumn ? this.node.bounds.y1 : this.node.bounds.x1; 181 | this.node.children.forEach((child, index) => { 182 | if (isColumn) { 183 | this.childrenSpaces.push(Math.floor(child.bounds.y1 - antChildEnd)); 184 | antChildEnd = child.bounds.y2; 185 | if (this.node.children.length - 1 == index) { 186 | this.childrenSpaces.push(Math.floor(this.node.bounds.y2 - antChildEnd)); 187 | } 188 | } else { 189 | this.childrenSpaces.push(Math.floor(child.bounds.x1 - antChildEnd)); 190 | antChildEnd = child.bounds.x2; 191 | if (this.node.children.length - 1 == index) { 192 | this.childrenSpaces.push(Math.floor(this.node.bounds.x2 - antChildEnd)); 193 | } 194 | } 195 | }); 196 | } 197 | } 198 | 199 | buildAlignment(child, index) { 200 | const widget = child.toDart(); 201 | const top = this.topDistance[index]; 202 | const bot = this.botDistance[index]; 203 | const left = this.leftDistance[index]; 204 | const right = this.rightDistance[index]; 205 | const isColumn = this.type == 'Column'; 206 | let resAlignment; 207 | let withStyledWidget = document.querySelector('input[name="simpleType"]'); 208 | withStyledWidget = withStyledWidget != null ? withStyledWidget.checked : null; 209 | if (isColumn) { 210 | if (left != right) { 211 | let auxRight = right == 0 && left == 0 ? 1 : right; 212 | const alignX = (left / (left + auxRight)); 213 | resAlignment = xdAlignmentToDartAlignment(alignX, 0.5); 214 | const aux = this.isEnd ? 'Alignment.centerRight' : this.isStart ? 'Alignment.centerLeft' : 'Alignment.center'; 215 | if (resAlignment != aux) { 216 | if (!withStyledWidget) { 217 | return `Align(alignment: ${resAlignment}, child: ${child.toDart()},)`; 218 | } 219 | if (resAlignment == 'Alignment.center') { 220 | return `${child.toDart()}.center()`; 221 | } 222 | return `${child.toDart()}.alignment(${resAlignment})`; 223 | } 224 | } 225 | } else { 226 | if (top != bot) { 227 | let auxBot = bot == 0 && top == 0 ? 1 : bot; 228 | const alignY = (top / (top + auxBot)); 229 | resAlignment = xdAlignmentToDartAlignment(0.5, alignY); 230 | const aux = this.isEnd ? 'Alignment.bottomCenter' : this.isStart ? 'Alignment.topCenter' : 'Alignment.center'; 231 | if (resAlignment != aux) { 232 | if (!withStyledWidget) { 233 | return `Align(alignment: ${resAlignment}, child: ${child.toDart()},)`; 234 | } 235 | if (resAlignment == 'Alignment.center') { 236 | return `${child.toDart()}.center()`; 237 | } 238 | return `${child.toDart()}.alignment(${resAlignment})`; 239 | } 240 | } 241 | } 242 | return widget; 243 | } 244 | 245 | buildPositioneds(child, index, withStyledWidget) { 246 | const widget = child.toDart(); 247 | const top = this.topDistance[index]; 248 | const bot = this.botDistance[index]; 249 | const left = this.leftDistance[index]; 250 | const right = this.rightDistance[index]; 251 | let horizontalPositioned = left == right ? '' : left < right ? left == 0 && this.isLeft ? '' : `left: ${left},` : right == 0 && this.isRight ? '' : `right: ${right},`; 252 | let verticalPositioned = top == bot ? '' : top < bot ? top == 0 && this.isTop ? '' : `top: ${top},` : bot == 0 && this.isBot ? '' : `bottom: ${bot},`; 253 | if (horizontalPositioned == '' && verticalPositioned == '') return widget; 254 | if (withStyledWidget) { 255 | horizontalPositioned = verticalPositioned == '' ? horizontalPositioned.substr(0, horizontalPositioned.length - 1) : horizontalPositioned; 256 | verticalPositioned = verticalPositioned.substr(0, verticalPositioned.length - 1); 257 | return `${widget}.positioned(${horizontalPositioned}${verticalPositioned})`; 258 | } 259 | return ` 260 | Positioned( 261 | ${horizontalPositioned} 262 | ${verticalPositioned} 263 | child: ${widget}, 264 | ) 265 | ` 266 | } 267 | 268 | getPositionedDistances() { 269 | this.topDistance = []; 270 | this.botDistance = []; 271 | this.leftDistance = []; 272 | this.rightDistance = []; 273 | this.node.children.forEach(child => { 274 | const top = child.bounds.y1 - this.node.bounds.y1; 275 | const bot = this.node.bounds.y2 - child.bounds.y2; 276 | const left = child.bounds.x1 - this.node.bounds.x1; 277 | const right = this.node.bounds.x2 - child.bounds.x2; 278 | this.topDistance.push(Math.floor(top)); 279 | this.botDistance.push(Math.floor(bot)); 280 | this.leftDistance.push(Math.floor(left)); 281 | this.rightDistance.push(Math.floor(right)); 282 | }); 283 | } 284 | 285 | getStackAlignment() { 286 | if (!this.isStack) return ''; 287 | let top = 0; 288 | let bot = 0; 289 | let left = 0; 290 | let right = 0; 291 | let verticalCenter = 0; 292 | let horizontalCenter = 0; 293 | for (let i = 0; i < this.topDistance.length; i++) { 294 | if (this.topDistance[i] == 0) top++; 295 | if (this.botDistance[i] == 0) bot++; 296 | if (this.leftDistance[i] == 0) left++; 297 | if (this.rightDistance[i] == 0) right++; 298 | if (this.rightDistance[i] == this.leftDistance[i]) horizontalCenter++; 299 | if (this.topDistance[i] == this.botDistance[i]) verticalCenter++; 300 | } 301 | this.isHorizontalCenter = horizontalCenter > left && horizontalCenter > right; 302 | this.isVerticalCenter = verticalCenter > top && verticalCenter > bot; 303 | this.isRight = !this.isHorizontalCenter && right > left; 304 | this.isBot = !this.isVerticalCenter && bot > top; 305 | this.isTop = !this.isBot && !this.isVerticalCenter; 306 | this.isLeft = !this.isRight && !this.isHorizontalCenter; 307 | let alignment; 308 | if (this.isRight) { 309 | if (this.isBot) alignment = 'bottomRight'; 310 | else if (this.isVerticalCenter) alignment = 'centerRight'; 311 | else if (this.isTop) alignment = 'topRight'; 312 | } else if (this.isLeft) { 313 | if (this.isBot) alignment = 'bottomLeft'; 314 | else if (this.isVerticalCenter) alignment = 'centerLeft'; 315 | } else { 316 | if (this.isBot) alignment = 'bottomCenter'; 317 | else if (this.isTop) alignment = 'topCenter'; 318 | else if (this.isVerticalCenter) alignment = 'center'; 319 | } 320 | if (!alignment) return; 321 | this.stackAlignment = `alignment: Alignment.${alignment},`; 322 | } 323 | } 324 | 325 | exports.Children = Children; 326 | 327 | Set.prototype.getByIdx = function (idx) { 328 | if (typeof idx !== 'number') throw new TypeError(`Argument idx must be a Number. Got [${idx}]`); 329 | 330 | let i = 0; 331 | for (let iter = this.keys(), curs = iter.next(); !curs.done; curs = iter.next(), i++) 332 | if (idx === i) return curs.value; 333 | 334 | throw new RangeError(`Index [${idx}] is out of range [0-${i - 1}]`); 335 | } 336 | -------------------------------------------------------------------------------- /src/widgets/util/color.js: -------------------------------------------------------------------------------- 1 | const assets = require("assets"); 2 | 3 | function getColor(color, opacity = 1.0, fromAsset = true) { 4 | const hexColor = color.toHex(true).replace("#", "").toUpperCase(); 5 | let colorResult = _colorToMaterialColor(`Color(0xFF${hexColor})`); 6 | if (fromAsset) { 7 | colorResult = _colorToAssetPanelColor(colorResult); 8 | } 9 | const withOpacity = _withOpacity((color.a / 255) * opacity); 10 | return `${colorResult}${withOpacity}`; 11 | } 12 | 13 | exports.getColor = getColor; 14 | 15 | function _withOpacity(opacity) { 16 | const { fix } = require("../../util"); 17 | opacity = fix(opacity); 18 | if (opacity != 1) return `.withOpacity(${opacity})`; 19 | return ``; 20 | } 21 | 22 | function _colorToMaterialColor(color) { 23 | if (materialColors[color] != null) 24 | return materialColors[color]; 25 | return 'const ' + color; 26 | } 27 | 28 | function _colorToAssetPanelColor(color) { 29 | const assetsColors = assets.colors.get(); 30 | for (let i = 0; i < assetsColors.length; i++) { 31 | const assetsColor = assetsColors[i]; 32 | const name = assetsColor.name != null ? assetsColor.name : `color${i + 1}`; 33 | if (!_isGradient(assetsColor)) { 34 | const generatedColor = getColor(assetsColor.color, 1, false) 35 | if (generatedColor == color) { 36 | const element = document.getElementById('widgetsPrexix'); 37 | let prefix = element != null ? element.value : element; 38 | if (!prefix) prefix = ''; 39 | return `${prefix}AppColors.${name}`; 40 | } 41 | } 42 | } 43 | return color; 44 | } 45 | 46 | function _isGradient(fill) { 47 | return fill.startY != null || (fill.colorStops != null && fill.colorStops.length > 0); 48 | } 49 | 50 | const materialColors = JSON.parse(JSON.stringify(JSON.parse(`{ 51 | "Color(0x00000000)" : "Colors.transparent", 52 | "Color(0xFF000000)" : "Colors.black", 53 | "Color(0xDD000000)" : "Colors.black87", 54 | "Color(0x8A000000)" : "Colors.black54", 55 | "Color(0x73000000)" : "Colors.black45", 56 | "Color(0x61000000)" : "Colors.black38", 57 | "Color(0x42000000)" : "Colors.black26", 58 | "Color(0x1F000000)" : "Colors.black12", 59 | "Color(0xFFFFFFFF)" : "Colors.white", 60 | "Color(0xB3FFFFFF)" : "Colors.white70", 61 | "Color(0x99FFFFFF)" : "Colors.white60", 62 | "Color(0x8AFFFFFF)" : "Colors.white54", 63 | "Color(0x62FFFFFF)" : "Colors.white38", 64 | "Color(0x4DFFFFFF)" : "Colors.white30", 65 | "Color(0x3DFFFFFF)" : "Colors.white24", 66 | "Color(0x1FFFFFFF)" : "Colors.white12", 67 | "Color(0x1AFFFFFF)" : "Colors.white10", 68 | "Color(0xFFFFEBEE)" : "Colors.red[50]", 69 | "Color(0xFFFFCDD2)" : "Colors.red[100]", 70 | "Color(0xFFEF9A9A)" : "Colors.red[200]", 71 | "Color(0xFFE57373)" : "Colors.red[300]", 72 | "Color(0xFFEF5350)" : "Colors.red[400]", 73 | "Color(0xFFF44336)" : "Colors.red", 74 | "Color(0xFFE53935)" : "Colors.red[600]", 75 | "Color(0xFFD32F2F)" : "Colors.red[700]", 76 | "Color(0xFFC62828)" : "Colors.red[800]", 77 | "Color(0xFFB71C1C)" : "Colors.red[900]", 78 | "Color(0xFFFF8A80)" : "Colors.redAccent[100]", 79 | "Color(0xFFFF5252)" : "Colors.redAccent", 80 | "Color(0xFFFF1744)" : "Colors.redAccent[400]", 81 | "Color(0xFFD50000)" : "Colors.redAccent[700]", 82 | "Color(0xFFFCE4EC)" : "Colors.pink[50]", 83 | "Color(0xFFF8BBD0)" : "Colors.pink[100]", 84 | "Color(0xFFF48FB1)" : "Colors.pink[200]", 85 | "Color(0xFFF06292)" : "Colors.pink[300]", 86 | "Color(0xFFEC407A)" : "Colors.pink[400]", 87 | "Color(0xFFE91E63)" : "Colors.pink", 88 | "Color(0xFFD81B60)" : "Colors.pink[600]", 89 | "Color(0xFFC2185B)" : "Colors.pink[700]", 90 | "Color(0xFFAD1457)" : "Colors.pink[800]", 91 | "Color(0xFF880E4F)" : "Colors.pink[900]", 92 | "Color(0xFFFF80AB)" : "Colors.pinkAccent[100]", 93 | "Color(0xFFFF4081)" : "Colors.pinkAccent", 94 | "Color(0xFFF50057)" : "Colors.pinkAccent[400]", 95 | "Color(0xFFC51162)" : "Colors.pinkAccent[700]", 96 | "Color(0xFFF3E5F5)" : "Colors.purple[50]", 97 | "Color(0xFFE1BEE7)" : "Colors.purple[100]", 98 | "Color(0xFFCE93D8)" : "Colors.purple[200]", 99 | "Color(0xFFBA68C8)" : "Colors.purple[300]", 100 | "Color(0xFFAB47BC)" : "Colors.purple[400]", 101 | "Color(0xFF9C27B0)" : "Colors.purple", 102 | "Color(0xFF8E24AA)" : "Colors.purple[600]", 103 | "Color(0xFF7B1FA2)" : "Colors.purple[700]", 104 | "Color(0xFF6A1B9A)" : "Colors.purple[800]", 105 | "Color(0xFF4A148C)" : "Colors.purple[900]", 106 | "Color(0xFFEA80FC)" : "Colors.purpleAccent[100]", 107 | "Color(0xFFE040FB)" : "Colors.purpleAccent[]", 108 | "Color(0xFFD500F9)" : "Colors.purpleAccent[400]", 109 | "Color(0xFFAA00FF)" : "Colors.purpleAccent[700]", 110 | "Color(0xFFEDE7F6)" : "Colors.deepPurple[50]", 111 | "Color(0xFFD1C4E9)" : "Colors.deepPurple[100]", 112 | "Color(0xFFB39DDB)" : "Colors.deepPurple[200]", 113 | "Color(0xFF9575CD)" : "Colors.deepPurple[300]", 114 | "Color(0xFF7E57C2)" : "Colors.deepPurple[400]", 115 | "Color(0xFF673AB7)" : "Colors.deepPurple", 116 | "Color(0xFF5E35B1)" : "Colors.deepPurple[600]", 117 | "Color(0xFF512DA8)" : "Colors.deepPurple[700]", 118 | "Color(0xFF4527A0)" : "Colors.deepPurple[800]", 119 | "Color(0xFF311B92)" : "Colors.deepPurple[900]", 120 | "Color(0xFFB388FF)" : "Colors.deepPurpleAccent[100]", 121 | "Color(0xFF7C4DFF)" : "Colors.deepPurpleAccent", 122 | "Color(0xFF651FFF)" : "Colors.deepPurpleAccent[400]", 123 | "Color(0xFF6200EA)" : "Colors.deepPurpleAccent[700]", 124 | "Color(0xFFE8EAF6)" : "Colors.indigo[50]", 125 | "Color(0xFFC5CAE9)" : "Colors.indigo[100]", 126 | "Color(0xFF9FA8DA)" : "Colors.indigo[200]", 127 | "Color(0xFF7986CB)" : "Colors.indigo[300]", 128 | "Color(0xFF5C6BC0)" : "Colors.indigo[400]", 129 | "Color(0xFF3F51B5)" : "Colors.indigo", 130 | "Color(0xFF3949AB)" : "Colors.indigo[600]", 131 | "Color(0xFF303F9F)" : "Colors.indigo[700]", 132 | "Color(0xFF283593)" : "Colors.indigo[800]", 133 | "Color(0xFF1A237E)" : "Colors.indigo[900]", 134 | "Color(0xFF8C9EFF)" : "Colors.indigoAccent[100]", 135 | "Color(0xFF536DFE)" : "Colors.indigoAccent", 136 | "Color(0xFF3D5AFE)" : "Colors.indigoAccent[400]", 137 | "Color(0xFF304FFE)" : "Colors.indigoAccent[700]", 138 | "Color(0xFFE3F2FD)" : "Colors.blue[50]", 139 | "Color(0xFFBBDEFB)" : "Colors.blue[100]", 140 | "Color(0xFF90CAF9)" : "Colors.blue[200]", 141 | "Color(0xFF64B5F6)" : "Colors.blue[300]", 142 | "Color(0xFF42A5F5)" : "Colors.blue[400]", 143 | "Color(0xFF2196F3)" : "Colors.blue", 144 | "Color(0xFF1E88E5)" : "Colors.blue[600]", 145 | "Color(0xFF1976D2)" : "Colors.blue[700]", 146 | "Color(0xFF1565C0)" : "Colors.blue[800]", 147 | "Color(0xFF0D47A1)" : "Colors.blue[900]", 148 | "Color(0xFF82B1FF)" : "Colors.blueAccent[100]", 149 | "Color(0xFF448AFF)" : "Colors.blueAccent", 150 | "Color(0xFF2979FF)" : "Colors.blueAccent[400]", 151 | "Color(0xFF2962FF)" : "Colors.blueAccent[700]", 152 | "Color(0xFFE1F5FE)" : "Colors.lightBlue[50]", 153 | "Color(0xFFB3E5FC)" : "Colors.lightBlue[100]", 154 | "Color(0xFF81D4FA)" : "Colors.lightBlue[200]", 155 | "Color(0xFF4FC3F7)" : "Colors.lightBlue[300]", 156 | "Color(0xFF29B6F6)" : "Colors.lightBlue[400]", 157 | "Color(0xFF03A9F4)" : "Colors.lightBlue", 158 | "Color(0xFF039BE5)" : "Colors.lightBlue[600]", 159 | "Color(0xFF0288D1)" : "Colors.lightBlue[700]", 160 | "Color(0xFF0277BD)" : "Colors.lightBlue[800]", 161 | "Color(0xFF01579B)" : "Colors.lightBlue[900]", 162 | "Color(0xFF80D8FF)" : "Colors.lightBlueAccent[100]", 163 | "Color(0xFF40C4FF)" : "Colors.lightBlueAccent", 164 | "Color(0xFF00B0FF)" : "Colors.lightBlueAccent[400]", 165 | "Color(0xFF0091EA)" : "Colors.lightBlueAccent[700]", 166 | "Color(0xFFE0F7FA)" : "Colors.cyan[50]", 167 | "Color(0xFFB2EBF2)" : "Colors.cyan[100]", 168 | "Color(0xFF80DEEA)" : "Colors.cyan[200]", 169 | "Color(0xFF4DD0E1)" : "Colors.cyan[300]", 170 | "Color(0xFF26C6DA)" : "Colors.cyan[400]", 171 | "Color(0xFF00BCD4)" : "Colors.cyan", 172 | "Color(0xFF00ACC1)" : "Colors.cyan[600]", 173 | "Color(0xFF0097A7)" : "Colors.cyan[700]", 174 | "Color(0xFF00838F)" : "Colors.cyan[800]", 175 | "Color(0xFF006064)" : "Colors.cyan[900]", 176 | "Color(0xFF84FFFF)" : "Colors.cyanAccent[100]", 177 | "Color(0xFF18FFFF)" : "Colors.cyanAccent", 178 | "Color(0xFF00E5FF)" : "Colors.cyanAccent[400]", 179 | "Color(0xFF00B8D4)" : "Colors.cyanAccent[700]", 180 | "Color(0xFFE0F2F1)" : "Colors.teal[50]", 181 | "Color(0xFFB2DFDB)" : "Colors.teal[100]", 182 | "Color(0xFF80CBC4)" : "Colors.teal[200]", 183 | "Color(0xFF4DB6AC)" : "Colors.teal[300]", 184 | "Color(0xFF26A69A)" : "Colors.teal[400]", 185 | "Color(0xFF009688)" : "Colors.teal", 186 | "Color(0xFF00897B)" : "Colors.teal[600]", 187 | "Color(0xFF00796B)" : "Colors.teal[700]", 188 | "Color(0xFF00695C)" : "Colors.teal[800]", 189 | "Color(0xFF004D40)" : "Colors.teal[900]", 190 | "Color(0xFFA7FFEB)" : "Colors.tealAccent[100]", 191 | "Color(0xFF64FFDA)" : "Colors.tealAccent", 192 | "Color(0xFF1DE9B6)" : "Colors.tealAccent[400]", 193 | "Color(0xFF00BFA5)" : "Colors.tealAccent[700]", 194 | "Color(0xFFE8F5E9)" : "Colors.green[50]", 195 | "Color(0xFFC8E6C9)" : "Colors.green[100]", 196 | "Color(0xFFA5D6A7)" : "Colors.green[200]", 197 | "Color(0xFF81C784)" : "Colors.green[300]", 198 | "Color(0xFF66BB6A)" : "Colors.green[400]", 199 | "Color(0xFF4CAF50)" : "Colors.green", 200 | "Color(0xFF43A047)" : "Colors.green[600]", 201 | "Color(0xFF388E3C)" : "Colors.green[700]", 202 | "Color(0xFF2E7D32)" : "Colors.green[800]", 203 | "Color(0xFF1B5E20)" : "Colors.green[900]", 204 | "Color(0xFFB9F6CA)" : "Colors.greenAccent[100]", 205 | "Color(0xFF69F0AE)" : "Colors.greenAccent", 206 | "Color(0xFF00E676)" : "Colors.greenAccent[400]", 207 | "Color(0xFF00C853)" : "Colors.greenAccent[700]", 208 | "Color(0xFFF1F8E9)" : "Colors.lightGreen[50]", 209 | "Color(0xFFDCEDC8)" : "Colors.lightGreen[100]", 210 | "Color(0xFFC5E1A5)" : "Colors.lightGreen[200]", 211 | "Color(0xFFAED581)" : "Colors.lightGreen[300]", 212 | "Color(0xFF9CCC65)" : "Colors.lightGreen[400]", 213 | "Color(0xFF8BC34A)" : "Colors.lightGreen", 214 | "Color(0xFF7CB342)" : "Colors.lightGreen[600]", 215 | "Color(0xFF689F38)" : "Colors.lightGreen[700]", 216 | "Color(0xFF558B2F)" : "Colors.lightGreen[800]", 217 | "Color(0xFF33691E)" : "Colors.lightGreen[900]", 218 | "Color(0xFFCCFF90)" : "Colors.lightGreenAccent[100]", 219 | "Color(0xFFB2FF59)" : "Colors.lightGreenAccent", 220 | "Color(0xFF76FF03)" : "Colors.lightGreenAccent[400]", 221 | "Color(0xFF64DD17)" : "Colors.lightGreenAccent[700]", 222 | "Color(0xFFF9FBE7)" : "Colors.lime[50]", 223 | "Color(0xFFF0F4C3)" : "Colors.lime[100]", 224 | "Color(0xFFE6EE9C)" : "Colors.lime[200]", 225 | "Color(0xFFDCE775)" : "Colors.lime[300]", 226 | "Color(0xFFD4E157)" : "Colors.lime[400]", 227 | "Color(0xFFCDDC39)" : "Colors.lime", 228 | "Color(0xFFC0CA33)" : "Colors.lime[600]", 229 | "Color(0xFFAFB42B)" : "Colors.lime[700]", 230 | "Color(0xFF9E9D24)" : "Colors.lime[800]", 231 | "Color(0xFF827717)" : "Colors.lime[900]", 232 | "Color(0xFFF4FF81)" : "Colors.limeAccent[100]", 233 | "Color(0xFFEEFF41)" : "Colors.limeAccent", 234 | "Color(0xFFC6FF00)" : "Colors.limeAccent[400]", 235 | "Color(0xFFAEEA00)" : "Colors.limeAccent[700]", 236 | "Color(0xFFFFFDE7)" : "Colors.yellow[50]", 237 | "Color(0xFFFFF9C4)" : "Colors.yellow[100]", 238 | "Color(0xFFFFF59D)" : "Colors.yellow[200]", 239 | "Color(0xFFFFF176)" : "Colors.yellow[300]", 240 | "Color(0xFFFFEE58)" : "Colors.yellow[400]", 241 | "Color(0xFFFFEB3B)" : "Colors.yellow", 242 | "Color(0xFFFDD835)" : "Colors.yellow[600]", 243 | "Color(0xFFFBC02D)" : "Colors.yellow[700]", 244 | "Color(0xFFF9A825)" : "Colors.yellow[800]", 245 | "Color(0xFFF57F17)" : "Colors.yellow[900]", 246 | "Color(0xFFFFFF8D)" : "Colors.yellowAccent[100]", 247 | "Color(0xFFFFFF00)" : "Colors.yellowAccent", 248 | "Color(0xFFFFEA00)" : "Colors.yellowAccent[400]", 249 | "Color(0xFFFFD600)" : "Colors.yellowAccent[700]", 250 | "Color(0xFFFFF8E1)" : "Colors.amber[50]", 251 | "Color(0xFFFFECB3)" : "Colors.amber[100]", 252 | "Color(0xFFFFE082)" : "Colors.amber[200]", 253 | "Color(0xFFFFD54F)" : "Colors.amber[300]", 254 | "Color(0xFFFFCA28)" : "Colors.amber[400]", 255 | "Color(0xFFFFC107)" : "Colors.amber", 256 | "Color(0xFFFFB300)" : "Colors.amber[600]", 257 | "Color(0xFFFFA000)" : "Colors.amber[700]", 258 | "Color(0xFFFF8F00)" : "Colors.amber[800]", 259 | "Color(0xFFFF6F00)" : "Colors.amber[900]", 260 | "Color(0xFFFFE57F)" : "Colors.amberAccent[100]", 261 | "Color(0xFFFFD740)" : "Colors.amberAccent", 262 | "Color(0xFFFFC400)" : "Colors.amberAccent[400]", 263 | "Color(0xFFFFAB00)" : "Colors.amberAccent[700]", 264 | "Color(0xFFFFF3E0)" : "Colors.orange[50]", 265 | "Color(0xFFFFE0B2)" : "Colors.orange[100]", 266 | "Color(0xFFFFCC80)" : "Colors.orange[200]", 267 | "Color(0xFFFFB74D)" : "Colors.orange[300]", 268 | "Color(0xFFFFA726)" : "Colors.orange[400]", 269 | "Color(0xFFFF9800)" : "Colors.orange", 270 | "Color(0xFFFB8C00)" : "Colors.orange[600]", 271 | "Color(0xFFF57C00)" : "Colors.orange[700]", 272 | "Color(0xFFEF6C00)" : "Colors.orange[800]", 273 | "Color(0xFFE65100)" : "Colors.orange[900]", 274 | "Color(0xFFFFD180)" : "Colors.orangeAccent[100]", 275 | "Color(0xFFFFAB40)" : "Colors.orangeAccent", 276 | "Color(0xFFFF9100)" : "Colors.orangeAccent[400]", 277 | "Color(0xFFFF6D00)" : "Colors.orangeAccent[700]", 278 | "Color(0xFFFBE9E7)" : "Colors.deepOrange[50]", 279 | "Color(0xFFFFCCBC)" : "Colors.deepOrange[100]", 280 | "Color(0xFFFFAB91)" : "Colors.deepOrange[200]", 281 | "Color(0xFFFF8A65)" : "Colors.deepOrange[300]", 282 | "Color(0xFFFF7043)" : "Colors.deepOrange[400]", 283 | "Color(0xFFFF5722)" : "Colors.deepOrange", 284 | "Color(0xFFF4511E)" : "Colors.deepOrange[600]", 285 | "Color(0xFFE64A19)" : "Colors.deepOrange[700]", 286 | "Color(0xFFD84315)" : "Colors.deepOrange[800]", 287 | "Color(0xFFBF360C)" : "Colors.deepOrange[900]", 288 | "Color(0xFFFF9E80)" : "Colors.deepOrangeAccent[100]", 289 | "Color(0xFFFF6E40)" : "Colors.deepOrangeAccent", 290 | "Color(0xFFFF3D00)" : "Colors.deepOrangeAccent[400]", 291 | "Color(0xFFDD2C00)" : "Colors.deepOrangeAccent[700]", 292 | "Color(0xFFEFEBE9)" : "Colors.brown[50]", 293 | "Color(0xFFD7CCC8)" : "Colors.brown[100]", 294 | "Color(0xFFBCAAA4)" : "Colors.brown[200]", 295 | "Color(0xFFA1887F)" : "Colors.brown[300]", 296 | "Color(0xFF8D6E63)" : "Colors.brown[400]", 297 | "Color(0xFF795548)" : "Colors.brown", 298 | "Color(0xFF6D4C41)" : "Colors.brown[600]", 299 | "Color(0xFF5D4037)" : "Colors.brown[700]", 300 | "Color(0xFF4E342E)" : "Colors.brown[800]", 301 | "Color(0xFF3E2723)" : "Colors.brown[900]", 302 | "Color(0xFFFAFAFA)" : "Colors.grey[50]", 303 | "Color(0xFFF5F5F5)" : "Colors.grey[100]", 304 | "Color(0xFFEEEEEE)" : "Colors.grey[200]", 305 | "Color(0xFFE0E0E0)" : "Colors.grey[300]", 306 | "Color(0xFFD6D6D6)" : "Colors.grey[350]", 307 | "Color(0xFFBDBDBD)" : "Colors.grey[400]", 308 | "Color(0xFF9E9E9E)" : "Colors.grey", 309 | "Color(0xFF757575)" : "Colors.grey[600]", 310 | "Color(0xFF616161)" : "Colors.grey[700]", 311 | "Color(0xFF424242)" : "Colors.grey[800]", 312 | "Color(0xFF303030)" : "Colors.grey[850]", 313 | "Color(0xFF212121)" : "Colors.grey[900]", 314 | "Color(0xFFECEFF1)" : "Colors.blueGrey[50]", 315 | "Color(0xFFCFD8DC)" : "Colors.blueGrey[100]", 316 | "Color(0xFFB0BEC5)" : "Colors.blueGrey[200]", 317 | "Color(0xFF90A4AE)" : "Colors.blueGrey[300]", 318 | "Color(0xFF78909C)" : "Colors.blueGrey[400]", 319 | "Color(0xFF607D8B)" : "Colors.blueGrey", 320 | "Color(0xFF546E7A)" : "Colors.blueGrey[600]", 321 | "Color(0xFF455A64)" : "Colors.blueGrey[700]", 322 | "Color(0xFF37474F)" : "Colors.blueGrey[800]", 323 | "Color(0xFF263238)" : "Colors.blueGrey[900]" 324 | }`))); 325 | -------------------------------------------------------------------------------- /src/widgets/util/google_fonts.js: -------------------------------------------------------------------------------- 1 | 2 | const googleFonts = ["aBeeZee", 3 | "abel", 4 | "abhayaLibre", 5 | "abrilFatface", 6 | "aclonica", 7 | "acme", 8 | "actor", 9 | "adamina", 10 | "adventPro", 11 | "aguafinaScript", 12 | "akronim", 13 | "aladin", 14 | "aldrich", 15 | "alef", 16 | "alegreya", 17 | "alegreyaSC", 18 | "alegreyaSans", 19 | "alegreyaSansSC", 20 | "aleo", 21 | "alexBrush", 22 | "alfaSlabOne", 23 | "alice", 24 | "alike", 25 | "alikeAngular", 26 | "allan", 27 | "allerta", 28 | "allertaStencil", 29 | "allura", 30 | "almendra", 31 | "almendraDisplay", 32 | "almendraSC", 33 | "amarante", 34 | "amaranth", 35 | "amaticSC", 36 | "amaticaSC", 37 | "amethysta", 38 | "amiko", 39 | "amiri", 40 | "amita", 41 | "anaheim", 42 | "andada", 43 | "andika", 44 | "annieUseYourTelescope", 45 | "anonymousPro", 46 | "antic", 47 | "anticDidone", 48 | "anticSlab", 49 | "anton", 50 | "arapey", 51 | "arbutus", 52 | "arbutusSlab", 53 | "architectsDaughter", 54 | "archivo", 55 | "archivoBlack", 56 | "archivoNarrow", 57 | "arefRuqaa", 58 | "arimaMadurai", 59 | "arimo", 60 | "arizonia", 61 | "armata", 62 | "arsenal", 63 | "artifika", 64 | "arvo", 65 | "arya", 66 | "asap", 67 | "asar", 68 | "asset", 69 | "assistant", 70 | "astloch", 71 | "asul", 72 | "athiti", 73 | "atma", 74 | "atomicAge", 75 | "aubrey", 76 | "audiowide", 77 | "autourOne", 78 | "average", 79 | "averageSans", 80 | "averiaGruesaLibre", 81 | "averiaLibre", 82 | "averiaSansLibre", 83 | "averiaSerifLibre", 84 | "b612", 85 | "b612Mono", 86 | "badScript", 87 | "bahiana", 88 | "bahianita", 89 | "baiJamjuree", 90 | "baloo", 91 | "balooBhai", 92 | "balooBhaijaan", 93 | "balooBhaina", 94 | "balooChettan", 95 | "balooDa", 96 | "balooPaaji", 97 | "balooTamma", 98 | "balooTammudu", 99 | "balooThambi", 100 | "balthazar", 101 | "bangers", 102 | "barlow", 103 | "barriecito", 104 | "barrio", 105 | "basic", 106 | "baumans", 107 | "belgrano", 108 | "bellefair", 109 | "belleza", 110 | "benchNine", 111 | "bentham", 112 | "berkshireSwash", 113 | "bethEllen", 114 | "bevan", 115 | "bigelowRules", 116 | "bigshotOne", 117 | "bilbo", 118 | "bilboSwashCaps", 119 | "bioRhyme", 120 | "biryani", 121 | "bitter", 122 | "blackAndWhitePicture", 123 | "blackHanSans", 124 | "blackOpsOne", 125 | "blinker", 126 | "bonbon", 127 | "boogaloo", 128 | "bowlbyOne", 129 | "bowlbyOneSC", 130 | "brawler", 131 | "breeSerif", 132 | "bubblegumSans", 133 | "bubblerOne", 134 | "buda", 135 | "buenard", 136 | "bungee", 137 | "bungeeHairline", 138 | "bungeeInline", 139 | "bungeeOutline", 140 | "bungeeShade", 141 | "butcherman", 142 | "butterflyKids", 143 | "cabin", 144 | "cabinSketch", 145 | "caesarDressing", 146 | "cagliostro", 147 | "cairo", 148 | "calligraffitti", 149 | "cambay", 150 | "cambo", 151 | "candal", 152 | "cantarell", 153 | "cantataOne", 154 | "cantoraOne", 155 | "capriola", 156 | "cardo", 157 | "carme", 158 | "carroisGothic", 159 | "carroisGothicSC", 160 | "carterOne", 161 | "catamaran", 162 | "caudex", 163 | "caveat", 164 | "caveatBrush", 165 | "cedarvilleCursive", 166 | "cevicheOne", 167 | "chakraPetch", 168 | "changa", 169 | "changaOne", 170 | "chango", 171 | "charm", 172 | "charmonman", 173 | "chathura", 174 | "chauPhilomeneOne", 175 | "chelaOne", 176 | "chelseaMarket", 177 | "cherryCreamSoda", 178 | "cherrySwash", 179 | "chewy", 180 | "chicle", 181 | "chivo", 182 | "chonburi", 183 | "cinzel", 184 | "cinzelDecorative", 185 | "clickerScript", 186 | "coda", 187 | "codaCaption", 188 | "codystar", 189 | "coiny", 190 | "combo", 191 | "comfortaa", 192 | "comingSoon", 193 | "concertOne", 194 | "condiment", 195 | "contrailOne", 196 | "convergence", 197 | "cookie", 198 | "copse", 199 | "corben", 200 | "cormorant", 201 | "cormorantGaramond", 202 | "cormorantInfant", 203 | "cormorantSC", 204 | "cormorantUnicase", 205 | "cormorantUpright", 206 | "courgette", 207 | "cousine", 208 | "coustard", 209 | "coveredByYourGrace", 210 | "craftyGirls", 211 | "creepster", 212 | "creteRound", 213 | "crimsonText", 214 | "croissantOne", 215 | "crushed", 216 | "cuprum", 217 | "cuteFont", 218 | "cutive", 219 | "cutiveMono", 220 | "dMSans", 221 | "dMSerifDisplay", 222 | "dMSerifText", 223 | "damion", 224 | "dancingScript", 225 | "darkerGrotesque", 226 | "davidLibre", 227 | "dawningofaNewDay", 228 | "daysOne", 229 | "dekko", 230 | "delius", 231 | "deliusSwashCaps", 232 | "deliusUnicase", 233 | "dellaRespira", 234 | "denkOne", 235 | "devonshire", 236 | "dhurjati", 237 | "didactGothic", 238 | "diplomata", 239 | "diplomataSC", 240 | "doHyeon", 241 | "dokdo", 242 | "domine", 243 | "donegalOne", 244 | "doppioOne", 245 | "dorsa", 246 | "dosis", 247 | "drSugiyama", 248 | "droidSans", 249 | "droidSansMono", 250 | "droidSerif", 251 | "duruSans", 252 | "dynalight", 253 | "eBGaramond", 254 | "eagleLake", 255 | "eastSeaDokdo", 256 | "eater", 257 | "economica", 258 | "eczar", 259 | "elMessiri", 260 | "electrolize", 261 | "elsie", 262 | "elsieSwashCaps", 263 | "emblemaOne", 264 | "emilysCandy", 265 | "encodeSans", 266 | "engagement", 267 | "englebert", 268 | "enriqueta", 269 | "ericaOne", 270 | "esteban", 271 | "euphoriaScript", 272 | "ewert", 273 | "exo", 274 | "exo2", 275 | "expletusSans", 276 | "fahkwang", 277 | "fanwoodText", 278 | "farro", 279 | "farsan", 280 | "fascinate", 281 | "fascinateInline", 282 | "fasterOne", 283 | "faunaOne", 284 | "faustina", 285 | "federant", 286 | "federo", 287 | "felipa", 288 | "fenix", 289 | "fingerPaint", 290 | "firaMono", 291 | "firaSans", 292 | "firaSansCondensed", 293 | "firaSansExtraCondensed", 294 | "fjallaOne", 295 | "fjordOne", 296 | "flamenco", 297 | "flavors", 298 | "fondamento", 299 | "fontdinerSwanky", 300 | "forum", 301 | "francoisOne", 302 | "frankRuhlLibre", 303 | "freckleFace", 304 | "frederickatheGreat", 305 | "fredokaOne", 306 | "fresca", 307 | "frijole", 308 | "fruktur", 309 | "fugazOne", 310 | "gFSDidot", 311 | "gFSNeohellenic", 312 | "gabriela", 313 | "gaegu", 314 | "gafata", 315 | "galada", 316 | "galdeano", 317 | "galindo", 318 | "gamjaFlower", 319 | "gayathri", 320 | "gentiumBasic", 321 | "gentiumBookBasic", 322 | "geo", 323 | "geostar", 324 | "geostarFill", 325 | "germaniaOne", 326 | "gidugu", 327 | "gildaDisplay", 328 | "giveYouGlory", 329 | "glassAntiqua", 330 | "glegoo", 331 | "gloriaHallelujah", 332 | "goblinOne", 333 | "gochiHand", 334 | "gorditas", 335 | "gothicA1", 336 | "goudyBookletter1911", 337 | "graduate", 338 | "grandHotel", 339 | "gravitasOne", 340 | "greatVibes", 341 | "grenze", 342 | "griffy", 343 | "gruppo", 344 | "gudea", 345 | "gugi", 346 | "gurajada", 347 | "habibi", 348 | "halant", 349 | "hammersmithOne", 350 | "hanalei", 351 | "hanaleiFill", 352 | "handlee", 353 | "happyMonkey", 354 | "harmattan", 355 | "headlandOne", 356 | "heebo", 357 | "hennyPenny", 358 | "herrVonMuellerhoff", 359 | "hiMelody", 360 | "hind", 361 | "hindGuntur", 362 | "hindMadurai", 363 | "hindSiliguri", 364 | "hindVadodara", 365 | "holtwoodOneSC", 366 | "homemadeApple", 367 | "homenaje", 368 | "iBMPlexMono", 369 | "iBMPlexSans", 370 | "iBMPlexSerif", 371 | "iMFellDWPica", 372 | "iMFellDWPicaSC", 373 | "iMFellDoublePica", 374 | "iMFellDoublePicaSC", 375 | "iMFellEnglish", 376 | "iMFellEnglishSC", 377 | "iMFellFrenchCanon", 378 | "iMFellFrenchCanonSC", 379 | "iMFellGreatPrimer", 380 | "iMFellGreatPrimerSC", 381 | "iceberg", 382 | "iceland", 383 | "imprima", 384 | "inconsolata", 385 | "inder", 386 | "indieFlower", 387 | "inika", 388 | "inknutAntiqua", 389 | "irishGrover", 390 | "istokWeb", 391 | "italiana", 392 | "italianno", 393 | "itim", 394 | "jacquesFrancois", 395 | "jacquesFrancoisShadow", 396 | "jaldi", 397 | "jimNightshade", 398 | "jockeyOne", 399 | "jollyLodger", 400 | "jomhuria", 401 | "josefinSans", 402 | "josefinSlab", 403 | "jotiOne", 404 | "jua", 405 | "judson", 406 | "julee", 407 | "juliusSansOne", 408 | "junge", 409 | "jura", 410 | "justAnotherHand", 411 | "justMeAgainDownHere", 412 | "k2D", 413 | "kadwa", 414 | "kalam", 415 | "kameron", 416 | "kanit", 417 | "kantumruy", 418 | "karla", 419 | "karma", 420 | "katibeh", 421 | "kaushanScript", 422 | "kavivanar", 423 | "kavoon", 424 | "kdamThmor", 425 | "keaniaOne", 426 | "kellySlab", 427 | "kenia", 428 | "khand", 429 | "khula", 430 | "kirangHaerang", 431 | "kiteOne", 432 | "knewave", 433 | "koHo", 434 | "kodchasan", 435 | "kottaOne", 436 | "kranky", 437 | "kreon", 438 | "kristi", 439 | "kronaOne", 440 | "krub", 441 | "kumarOne", 442 | "kumarOneOutline", 443 | "kurale", 444 | "laBelleAurore", 445 | "lacquer", 446 | "laila", 447 | "lakkiReddy", 448 | "lalezar", 449 | "lancelot", 450 | "lateef", 451 | "lato", 452 | "leagueScript", 453 | "leckerliOne", 454 | "ledger", 455 | "lekton", 456 | "lemon", 457 | "lemonada", 458 | "libreBaskerville", 459 | "libreFranklin", 460 | "lifeSavers", 461 | "lilitaOne", 462 | "lilyScriptOne", 463 | "limelight", 464 | "lindenHill", 465 | "liuJianMaoCao", 466 | "livvic", 467 | "lobster", 468 | "lobsterTwo", 469 | "londrinaOutline", 470 | "londrinaShadow", 471 | "londrinaSketch", 472 | "londrinaSolid", 473 | "longCang", 474 | "lora", 475 | "loveYaLikeASister", 476 | "lovedbytheKing", 477 | "loversQuarrel", 478 | "luckiestGuy", 479 | "lusitana", 480 | "lustria", 481 | "mPLUSRounded1c", 482 | "maShanZheng", 483 | "macondo", 484 | "macondoSwashCaps", 485 | "mada", 486 | "magra", 487 | "maidenOrange", 488 | "maitree", 489 | "majorMonoDisplay", 490 | "mako", 491 | "mali", 492 | "mallanna", 493 | "mandali", 494 | "manjari", 495 | "manuale", 496 | "marcellus", 497 | "marcellusSC", 498 | "marckScript", 499 | "margarine", 500 | "markoOne", 501 | "marmelad", 502 | "martel", 503 | "martelSans", 504 | "marvel", 505 | "mate", 506 | "mateSC", 507 | "mavenPro", 508 | "mcLaren", 509 | "meddon", 510 | "medievalSharp", 511 | "medulaOne", 512 | "meeraInimai", 513 | "megrim", 514 | "meieScript", 515 | "merienda", 516 | "meriendaOne", 517 | "merriweather", 518 | "merriweatherSans", 519 | "metalMania", 520 | "metamorphous", 521 | "metrophobic", 522 | "michroma", 523 | "milonga", 524 | "miltonian", 525 | "miltonianTattoo", 526 | "mina", 527 | "miniver", 528 | "miriamLibre", 529 | "mirza", 530 | "missFajardose", 531 | "mitr", 532 | "modak", 533 | "modernAntiqua", 534 | "mogra", 535 | "molengo", 536 | "molle", 537 | "monda", 538 | "monofett", 539 | "monoton", 540 | "monsieurLaDoulaise", 541 | "montaga", 542 | "montez", 543 | "montserrat", 544 | "montserratAlternates", 545 | "montserratSubrayada", 546 | "mountainsofChristmas", 547 | "mouseMemoirs", 548 | "mrBedfort", 549 | "mrDafoe", 550 | "mrDeHaviland", 551 | "mrsSaintDelafield", 552 | "mrsSheppards", 553 | "mukta", 554 | "muktaMahee", 555 | "muktaMalar", 556 | "muktaVaani", 557 | "muli", 558 | "mysteryQuest", 559 | "nTR", 560 | "nanumBrushScript", 561 | "nanumGothic", 562 | "nanumGothicCoding", 563 | "nanumMyeongjo", 564 | "nanumPenScript", 565 | "neucha", 566 | "neuton", 567 | "newRocker", 568 | "newsCycle", 569 | "niconne", 570 | "niramit", 571 | "nixieOne", 572 | "nobile", 573 | "norican", 574 | "nosifer", 575 | "notable", 576 | "nothingYouCouldDo", 577 | "noticiaText", 578 | "notoColorEmojiCompat", 579 | "notoSans", 580 | "notoSerif", 581 | "novaCut", 582 | "novaFlat", 583 | "novaMono", 584 | "novaOval", 585 | "novaRound", 586 | "novaScript", 587 | "novaSlim", 588 | "novaSquare", 589 | "numans", 590 | "nunito", 591 | "nunitoSans", 592 | "odorMeanChey", 593 | "offside", 594 | "oldStandardTT", 595 | "oldenburg", 596 | "oleoScript", 597 | "oleoScriptSwashCaps", 598 | "openSans", 599 | "oranienbaum", 600 | "orbitron", 601 | "oregano", 602 | "orienta", 603 | "originalSurfer", 604 | "oswald", 605 | "overtheRainbow", 606 | "overlock", 607 | "overlockSC", 608 | "overpass", 609 | "overpassMono", 610 | "ovo", 611 | "oxygen", 612 | "oxygenMono", 613 | "pTMono", 614 | "pTSans", 615 | "pTSansCaption", 616 | "pTSansNarrow", 617 | "pTSerif", 618 | "pTSerifCaption", 619 | "pacifico", 620 | "padauk", 621 | "palanquin", 622 | "palanquinDark", 623 | "pangolin", 624 | "paprika", 625 | "parisienne", 626 | "passeroOne", 627 | "passionOne", 628 | "pathwayGothicOne", 629 | "patrickHand", 630 | "patrickHandSC", 631 | "pattaya", 632 | "patuaOne", 633 | "pavanam", 634 | "paytoneOne", 635 | "peddana", 636 | "peralta", 637 | "permanentMarker", 638 | "petitFormalScript", 639 | "petrona", 640 | "philosopher", 641 | "piedra", 642 | "pinyonScript", 643 | "pirataOne", 644 | "plaster", 645 | "play", 646 | "playball", 647 | "playfairDisplay", 648 | "playfairDisplaySC", 649 | "podkova", 650 | "poiretOne", 651 | "pollerOne", 652 | "poly", 653 | "pompiere", 654 | "pontanoSans", 655 | "poorStory", 656 | "poppins", 657 | "portLligatSans", 658 | "portLligatSlab", 659 | "pragatiNarrow", 660 | "prata", 661 | "pressStart2P", 662 | "pridi", 663 | "princessSofia", 664 | "prociono", 665 | "prompt", 666 | "prostoOne", 667 | "prozaLibre", 668 | "puritan", 669 | "purplePurse", 670 | "quando", 671 | "quantico", 672 | "quattrocento", 673 | "quattrocentoSans", 674 | "questrial", 675 | "quicksand", 676 | "quintessential", 677 | "qwigley", 678 | "racingSansOne", 679 | "radley", 680 | "rajdhani", 681 | "rakkas", 682 | "raleway", 683 | "ralewayDots", 684 | "ramabhadra", 685 | "ramaraja", 686 | "rambla", 687 | "rammettoOne", 688 | "ranchers", 689 | "rancho", 690 | "ranga", 691 | "rasa", 692 | "rationale", 693 | "raviPrakash", 694 | "redHatDisplay", 695 | "redHatText", 696 | "redressed", 697 | "reemKufi", 698 | "reenieBeanie", 699 | "revalia", 700 | "rhodiumLibre", 701 | "ribeye", 702 | "ribeyeMarrow", 703 | "righteous", 704 | "risque", 705 | "roboto", 706 | "robotoMono", 707 | "robotoSlab", 708 | "rochester", 709 | "rockSalt", 710 | "rokkitt", 711 | "romanesco", 712 | "ropaSans", 713 | "rosario", 714 | "rosarivo", 715 | "rougeScript", 716 | "rozhaOne", 717 | "rubik", 718 | "rubikMonoOne", 719 | "ruda", 720 | "rufina", 721 | "rugeBoogie", 722 | "ruluko", 723 | "rumRaisin", 724 | "ruslanDisplay", 725 | "russoOne", 726 | "ruthie", 727 | "rye", 728 | "sacramento", 729 | "sahitya", 730 | "sail", 731 | "saira", 732 | "sairaStencilOne", 733 | "salsa", 734 | "sanchez", 735 | "sancreek", 736 | "sansita", 737 | "sarala", 738 | "sarina", 739 | "sarpanch", 740 | "satisfy", 741 | "sawarabiGothic", 742 | "sawarabiMincho", 743 | "scada", 744 | "scheherazade", 745 | "schoolbell", 746 | "scopeOne", 747 | "seaweedScript", 748 | "secularOne", 749 | "sedgwickAve", 750 | "sedgwickAveDisplay", 751 | "sevillana", 752 | "seymourOne", 753 | "shadowsIntoLight", 754 | "shadowsIntoLightTwo", 755 | "shanti", 756 | "share", 757 | "shareTech", 758 | "shareTechMono", 759 | "shojumaru", 760 | "shortStack", 761 | "shrikhand", 762 | "sigmarOne", 763 | "signika", 764 | "signikaNegative", 765 | "simonetta", 766 | "sintony", 767 | "sirinStencil", 768 | "sixCaps", 769 | "skranji", 770 | "slabo13px", 771 | "slabo27px", 772 | "slackey", 773 | "smokum", 774 | "smythe", 775 | "sniglet", 776 | "snippet", 777 | "snowburstOne", 778 | "sofadiOne", 779 | "sofia", 780 | "songMyung", 781 | "sonsieOne", 782 | "sortsMillGoudy", 783 | "sourceCodePro", 784 | "sourceSansPro", 785 | "sourceSerifPro", 786 | "spaceMono", 787 | "specialElite", 788 | "spectral", 789 | "spectralSC", 790 | "spicyRice", 791 | "spinnaker", 792 | "spirax", 793 | "squadaOne", 794 | "sreeKrushnadevaraya", 795 | "sriracha", 796 | "srisakdi", 797 | "staatliches", 798 | "stalemate", 799 | "stalinistOne", 800 | "stardosStencil", 801 | "stintUltraCondensed", 802 | "stintUltraExpanded", 803 | "stoke", 804 | "strait", 805 | "stylish", 806 | "sueEllenFrancisco", 807 | "suezOne", 808 | "sumana", 809 | "sunflower", 810 | "sunshiney", 811 | "supermercadoOne", 812 | "sura", 813 | "suranna", 814 | "suravaram", 815 | "swankyandMooMoo", 816 | "syncopate", 817 | "tajawal", 818 | "tangerine", 819 | "tauri", 820 | "taviraj", 821 | "teko", 822 | "telex", 823 | "tenaliRamakrishna", 824 | "tenorSans", 825 | "textMeOne", 826 | "thasadith", 827 | "theGirlNextDoor", 828 | "tienne", 829 | "tillana", 830 | "timmana", 831 | "tinos", 832 | "titanOne", 833 | "titilliumWeb", 834 | "tradeWinds", 835 | "trirong", 836 | "trocchi", 837 | "trochut", 838 | "trykker", 839 | "tulpenOne", 840 | "ubuntu", 841 | "ubuntuMono", 842 | "ultra", 843 | "uncialAntiqua", 844 | "underdog", 845 | "unicaOne", 846 | "unifrakturCook", 847 | "unifrakturMaguntia", 848 | "unkempt", 849 | "unlock", 850 | "unna", 851 | "vT323", 852 | "vampiroOne", 853 | "varela", 854 | "varelaRound", 855 | "vastShadow", 856 | "vesperLibre", 857 | "vibes", 858 | "vibur", 859 | "vidaloka", 860 | "viga", 861 | "voces", 862 | "volkhov", 863 | "vollkorn", 864 | "vollkornSC", 865 | "voltaire", 866 | "waitingfortheSunrise", 867 | "wallpoet", 868 | "walterTurncoat", 869 | "warnes", 870 | "wellfleet", 871 | "wendyOne", 872 | "wireOne", 873 | "workSans", 874 | "yanoneKaffeesatz", 875 | "yantramanav", 876 | "yatraOne", 877 | "yellowtail", 878 | "yeonSung", 879 | "yesevaOne", 880 | "yesteryear", 881 | "youTubeSans", 882 | "youTubeSansDark", 883 | "yrsa", 884 | "zCOOLKuaiLe", 885 | "zCOOLQingKeHuangYou", 886 | "zCOOLXiaoWei", 887 | "zeyada", 888 | "zhiMangXing", 889 | "zillaSlab", 890 | "zillaSlabHighlight", 891 | ]; 892 | 893 | module.exports = { 894 | googleFonts: googleFonts, 895 | }; 896 | '' --------------------------------------------------------------------------------