├── .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 + ``;
5 | }
6 | const outputText = ``;
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 |
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 = ``;
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 | ''
--------------------------------------------------------------------------------