├── .appcast.xml ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .webpackrc.js ├── LICENSE ├── README.md ├── docs ├── Anto.sketch ├── banner.png ├── button-anima.png ├── button-anto.png ├── doc-1.png ├── doc-2.png ├── qrcode-anto.png ├── qrcode-canisminor.png └── qrcode-public.png ├── icon.png ├── package.json ├── panel ├── components │ ├── ButtonGroup.js │ ├── Cell.js │ ├── Check.js │ ├── Close.js │ ├── Iconfont.js │ ├── ListView.js │ ├── Loading.js │ ├── Title.js │ ├── View.js │ └── index.js ├── data │ ├── color.json │ └── word.json ├── index.css ├── index.ejs ├── index.js ├── models │ ├── check.js │ ├── colors.js │ ├── config.js │ ├── docs.js │ ├── store.js │ ├── symbols.js │ └── username.js ├── router.js ├── routes │ ├── Colors.js │ ├── Dev.js │ ├── Docs.js │ ├── Layer.js │ ├── Line.js │ ├── Note.js │ ├── Plate.js │ ├── Setting.js │ ├── Symbols.js │ └── index.js └── utils │ ├── PostMessage.js │ └── request.js ├── preview ├── .webpackrc.js ├── package.json ├── public │ ├── data.js │ └── preview │ │ ├── Page 1 (视觉) 1231.png │ │ ├── Page 2 (交互) 1231.png │ │ └── a (视觉) 1231.png └── src │ ├── index.css │ ├── index.ejs │ ├── index.js │ ├── models │ └── preview.js │ ├── router.js │ └── routes │ └── index.js ├── public ├── icon.png ├── iconfont │ ├── demo.css │ ├── iconfont.css │ ├── iconfont.eot │ ├── iconfont.svg │ ├── iconfont.ttf │ ├── iconfont.woff │ └── iconfont.woff2 ├── logo.png └── minicon.png ├── scripts ├── .gitignore ├── ding.js └── preview.js ├── src ├── api │ ├── create.js │ ├── layer.js │ ├── library.js │ ├── native.js │ ├── setting.js │ └── ui.js ├── index.js ├── library.js ├── manifest.json ├── models │ ├── devColor.js │ ├── devNumber.js │ ├── devSymbol.js │ ├── devTest.js │ ├── handleBlender.js │ ├── handleBuildLocalSymbol.js │ ├── handleChange.js │ ├── handleColor.js │ ├── handleDash.js │ ├── handleExport.js │ ├── handleFrontBack.js │ ├── handleFrontBackLite.js │ ├── handleHeight.js │ ├── handleIgnore.js │ ├── handleLayout.js │ ├── handleLine.js │ ├── handleLocalSymbol.js │ ├── handleNote.js │ ├── handlePlate.js │ ├── handleSort.js │ ├── handleSymbol.js │ └── handleTitle.js ├── options.js ├── preview.json ├── router.js ├── sketch.js ├── update.js └── utils.js └── test.json /.appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules/** 2 | **/NameOrganizer.sketchplugin/** 3 | **/dist/** 4 | **/test/** 5 | SketchAPI -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser : "babel-eslint", 3 | extends : [ 4 | "standard", 5 | "plugin:flowtype/recommended", 6 | "plugin:react/recommended", 7 | "prettier", 8 | "prettier/flowtype", 9 | "prettier/react", 10 | "prettier/standard" 11 | ], 12 | plugins : [ 13 | "flowtype", 14 | "react", 15 | "prettier", 16 | "standard" 17 | ], 18 | parserOptions: { 19 | "ecmaFeatures": { 20 | "experimentalObjectRestSpread": true, 21 | "jsx" : true 22 | } 23 | }, 24 | globals : { 25 | "window" : true, 26 | "location" : true, 27 | "document" : true, 28 | "navigator" : true, 29 | "localStorage" : true, 30 | "$isDev" : true, 31 | "define" : true, 32 | "fetch" : true, 33 | "AppController" : true, 34 | "MSSliceLayer" : true, 35 | "MSLayerMovement" : true, 36 | "NSBezierPath" : true, 37 | "NSMakePoint" : true, 38 | "MSShapePathLayer" : true, 39 | "MSPath" : true, 40 | "NSDocumentController": true, 41 | "NSWorkspace" : true, 42 | "NSURL" : true, 43 | "NSSavePanel" : true, 44 | "NSString" : true, 45 | "MSTextLayer" : true, 46 | "MSRectangleShape" : true, 47 | "NSMakeRect" : true, 48 | "MSColor" : true 49 | }, 50 | env : { 51 | "es6" : true, 52 | "node": true 53 | }, 54 | rules : { 55 | "prettier/prettier" : [ 56 | 2, { 57 | "printWidth" : 100, 58 | "singleQuote" : true, 59 | "trailingComma": "es5", 60 | "parser" : "flow" 61 | } 62 | ], 63 | "no-unused-vars" : 1, 64 | "react/display-name" : 0, 65 | "react/react-in-jsx-scope" : 0, 66 | "react/prop-types" : 0, 67 | "react/no-children-prop" : 0, 68 | "react/no-string-refs" : 0, 69 | "react/no-unescaped-entities": 0, 70 | "handle-callback-err" : 1, 71 | "new-cap" : 0, 72 | "node/no-deprecated-api" : 1 73 | } 74 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # npm 2 | node_modules 3 | .npm 4 | npm-debug.log 5 | 6 | # mac 7 | .DS_Store 8 | yarn.lock 9 | yarn-error.log 10 | 11 | # other 12 | test 13 | dist 14 | .idea 15 | ./docs/AFUX 色彩库 16 | *.sketchplugin 17 | *.zip 18 | -------------------------------------------------------------------------------- /.webpackrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | entry : "./panel/index.js", 3 | disableCSSModules : true, 4 | ignoreMomentLocale : true, 5 | theme : { 6 | "@primary-color": "#2A72FF", 7 | "@text-color": "#999", 8 | "@heading-color": "#999", 9 | "@border-color-base":"rgba(100,100,100,.3)", 10 | "@input-bg":"rgba(255,255,255,.03)", 11 | "@input-placeholder-color":"rgba(100,100,100,.5)" 12 | }, 13 | html : { 14 | "template": "./panel/index.ejs" 15 | }, 16 | define : { 17 | "$dirname": __dirname, 18 | "$isDev" : process.env.NODE_ENV === "development" 19 | }, 20 | extraBabelPlugins : [ 21 | 'lodash', 22 | ['import', {libraryName: 'antd', libraryDirectory: 'es', style: true}] 23 | ], 24 | env : { 25 | development: { 26 | extraBabelPlugins: [ 27 | "dva-hmr", 28 | ["babel-plugin-styled-components", { displayName: true }] 29 | ] 30 | }, 31 | production : { 32 | browserslist : ['iOS >= 8', 'Android >= 4'], 33 | } 34 | } 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2019 canisminor1990 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > 暂停维护,将于 21 年 12 月底合并至 Kitchen 对外版 https://kitchen.antfin-inc.com/ 2 | 3 | 4 | # Anto 5 | 6 | ![banner.png](https://raw.githubusercontent.com/canisminor1990/anto/master/docs/banner.png) 7 | 8 | [![GitHub release](https://img.shields.io/github/release/canisminor1990/anto.svg)](https://github.com/canisminor1990/anto/releases) [![GitHub release](https://img.shields.io/badge/Works%20with-Sketch%20Runner-blue.svg?colorB=308ADF)](http://bit.ly/SketchRunnerWebsite) [![](https://img.shields.io/github/downloads/canisminor1990/anto/total.svg)](https://github.com/canisminor1990/anto/releases) 9 | 10 | 一款为 AFUX 设计师提升工作效率的 Sketch 工具集。 11 | 12 | **Anto** 取自 **Ant** + **Auto**, 期待成为大家设计生活中必不可缺的自动化生产力工具。 13 | 14 | > 目前工具各项功能都基于 2x,1x 支持已添加到 🐜ToDoList 15 | 16 | ## 插件文档 17 | 18 | - 📘[公共语雀](https://www.yuque.com/canisminor/anto/readme) 19 | - 📘[内部语雀](https://yuque.antfin-inc.com/afux-design/anto/readme) 20 | 21 |
22 | 23 | ## 获取与安装 24 | 25 | **Anto** 依赖 **Anima** 部分布局功能,请按顺安装: 26 | 27 | ### 1.版本要求 28 | 29 | 系统需升级 **MacOS Mojave**, Sketch 版本 **>= 52** 30 | 31 | ### 2.安装 Anima 32 | 33 | 点击下方按钮下载 `AnimaToolkit` : 34 | 35 | [](https://animaapp.s3.amazonaws.com/sketchplugin/toolkit/AnimaToolkitPlugin.zip) 36 | 37 | ### 3.安装 Anto 38 | 39 | 提供三种 Anto 的下载方式 40 | 41 | #### 直接下载 42 | 43 | [](https://github.com/canisminor1990/anto/releases) 44 | 45 | 1. 点击上方按钮下载 `Anto` : 46 | 2. 打开 **Anto.sketchplugin** 即可自动安装 47 | 48 |
49 | 50 | #### 使用 Sketchpacks 安装 51 | 52 | 53 | 54 |
55 | 56 | #### 使用 Runner 安装 57 | 58 | 59 | 60 | 1. + ' 打开 **Runner**; 61 | 2. 切换至 **install** 标签; 62 | 3. 输入 **Anto**; 63 | 4. 选择  **Anto** 并 回车. 64 | 65 |
66 | 67 | ### 4.重启 💎Sketch 68 | 69 |
70 | 71 | ## 使用指南 72 | 73 | ![](https://raw.githubusercontent.com/canisminor1990/anto/master/docs/doc-2.png) 74 | 75 | - ✨ [使用指南-组件](https://www.yuque.com/canisminor/anto/symbol) 76 | - ✨ [使用指南-连线](https://www.yuque.com/canisminor/anto/lines) 77 | - ✨ [使用指南-图层](https://www.yuque.com/canisminor/anto/layers) 78 | - ✨ [使用指南-标注](https://www.yuque.com/canisminor/anto/note) 79 | - ✨ [使用指南-制标&制版](https://www.yuque.com/canisminor/anto/publish) 80 | - ✨ [使用指南-其他](https://www.yuque.com/canisminor/anto/others) 81 | 82 |
83 | 84 | ## Q&A 85 | 86 | #### 1.安装后有重复的插件 87 | 88 | 如果之前已经安装过 Anto 或者 AFUX Tools 插件的话,请先将旧插件卸载后再安装新的插件: 89 | 90 | - 点击 Plugin -- Manage Plugins 91 | - Uninstall "Anto" 删除插件 92 | - 点击 Preferences -- Libraries 93 | - Remove "AFUX 输出组件" & "AFUX 交互组件" 删除 Library 94 | 95 | ![](https://raw.githubusercontent.com/canisminor1990/anto/master/docs/doc-1.png) 96 | 97 | #### 2. 更新插件不生效 98 | 99 | 有时会发生插件会自动更新失败导致插件消失,如果失效,请参照 Q&A 第一条删除插件后在此页面重新下载插件。 100 | 101 |
102 | 103 | ## 反馈&帮助 104 | 105 | 若帮助文档不能解决你的问题,可以钉钉扫码加入 Anto 交流群:新版体验和激情讨论都在这里,赶快来反馈问题和意见吧~🤘😘! 106 | 107 |
108 | 109 | 110 |
111 | 112 | ## License (许可) 113 | 114 | [Apache 2.0](https://github.com/canisminor1990/anto/blob/master/LICENSE) 115 | -------------------------------------------------------------------------------- /docs/Anto.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/docs/Anto.sketch -------------------------------------------------------------------------------- /docs/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/docs/banner.png -------------------------------------------------------------------------------- /docs/button-anima.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/docs/button-anima.png -------------------------------------------------------------------------------- /docs/button-anto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/docs/button-anto.png -------------------------------------------------------------------------------- /docs/doc-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/docs/doc-1.png -------------------------------------------------------------------------------- /docs/doc-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/docs/doc-2.png -------------------------------------------------------------------------------- /docs/qrcode-anto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/docs/qrcode-anto.png -------------------------------------------------------------------------------- /docs/qrcode-canisminor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/docs/qrcode-canisminor.png -------------------------------------------------------------------------------- /docs/qrcode-public.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/docs/qrcode-public.png -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Anto", 3 | "version": "1.2.9", 4 | "description": "Sketch tools for AFUX", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/canisminor1990/anto.git" 8 | }, 9 | "author": { 10 | "name": "CanisMinor", 11 | "email": "i@canisminor.cc", 12 | "url": "https://canisminor.cc/" 13 | }, 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/canisminor1990/anto/issues" 17 | }, 18 | "homepage": "https://github.com/canisminor1990/anto", 19 | "main": "Anto.sketchplugin", 20 | "engines": { 21 | "sketch": ">=50.0" 22 | }, 23 | "skpm": { 24 | "name": "Anto", 25 | "manifest": "src/manifest.json", 26 | "main": "Anto.sketchplugin", 27 | "assets": [ 28 | "dist/**/*" 29 | ] 30 | }, 31 | "appcast": ".appcast.xml", 32 | "scripts": { 33 | "start": "yarn build:panel && concurrently \"yarn start:panel\" \"yarn start:plugin\"", 34 | "start:plugin": "yarn dev && cross-env NODE_ENV=development skpm-build --watch", 35 | "start:panel": "cross-env ESLINT=none roadhog dev", 36 | "build": "rm -rf Anto.sketchplugin && yarn build:panel && yarn build:plugin", 37 | "build:plugin": "cross-env NODE_ENV=production skpm-build", 38 | "build:panel": "cross-env ESLINT=none roadhog build", 39 | "build:symbol": "node ./scripts/symbol.js", 40 | "build:preview": "cd preview && yarn build && cd ../ && node ./scripts/preview.js", 41 | "build:color": "node ./scripts/color.js && rm -r './docs/AFUX 色彩库'", 42 | "build:word": "node ./scripts/word.js", 43 | "publish": "skpm publish", 44 | "link": "skpm-link", 45 | "dev": "defaults write ~/Library/Preferences/com.bohemiancoding.sketch3.plist AlwaysReloadScript -bool YES", 46 | "lint": "lint-staged", 47 | "lint:es": "eslint --fix --ext .js src && eslint --fix --ext .js panel && eslint --fix --ext .js preview" 48 | }, 49 | "pre-commit": [ 50 | "lint" 51 | ], 52 | "lint-staged": { 53 | "*.md": [ 54 | "prettier --trailing-comma all --single-quote --write", 55 | "git add" 56 | ], 57 | "./package.json": [ 58 | "prettier --trailing-comma all --single-quote --write", 59 | "git add" 60 | ], 61 | "src/**/*.js": [ 62 | "eslint --fix", 63 | "git add" 64 | ], 65 | "panel/**/*.js": [ 66 | "eslint --fix", 67 | "git add" 68 | ], 69 | "preivew/**/*.js": [ 70 | "eslint --fix", 71 | "git add" 72 | ], 73 | "panel/**/*.scss": [ 74 | "prettier --trailing-comma all --single-quote --write", 75 | "git add" 76 | ], 77 | "preview/**/*.scss": [ 78 | "prettier --trailing-comma all --single-quote --write", 79 | "git add" 80 | ] 81 | }, 82 | "dependencies": { 83 | "antd": "^3.10.9", 84 | "dva": "^2.4.1", 85 | "dva-loading": "^2.0.6", 86 | "lodash": "^4.17.11", 87 | "react": "^16.6.3", 88 | "react-copy-to-clipboard": "^5.0.1", 89 | "react-dom": "^16.6.3", 90 | "sketch-module-web-view": "^3.2.2" 91 | }, 92 | "devDependencies": { 93 | "@babel/core": "^7.1.2", 94 | "@babel/preset-env": "^7.1.0", 95 | "@babel/preset-stage-0": "^7.0.0", 96 | "@babel/runtime": "^7.1.2", 97 | "@skpm/builder": "^0.5.11", 98 | "@skpm/dialog": "^0.2.4", 99 | "@skpm/extract-loader": "^2.0.2", 100 | "@skpm/fs": "^0.2.2", 101 | "babel-plugin-dva-hmr": "^0.4.1", 102 | "babel-plugin-import": "^1.10.0", 103 | "babel-plugin-lodash": "^3.3.4", 104 | "babel-plugin-transform-decorators-legacy": "^1.3.5", 105 | "compare-versions": "^3.4.0", 106 | "concurrently": "^3.6.1", 107 | "cross-env": "^5.2.0", 108 | "eslint": "^4.13.1", 109 | "eslint-config-prettier": "^2.9.0", 110 | "eslint-config-standard": "^10.2.1", 111 | "eslint-plugin-flowtype": "^2.37.0", 112 | "eslint-plugin-import": "^2.7.0", 113 | "eslint-plugin-node": "^5.2.0", 114 | "eslint-plugin-prettier": "^2.3.1", 115 | "eslint-plugin-promise": "^3.5.0", 116 | "eslint-plugin-react": "^7.4.0", 117 | "eslint-plugin-standard": "^3.0.1", 118 | "expect": "^21.2.1", 119 | "express": "^4.16.4", 120 | "file-saver": "^2.0.0", 121 | "fs-extra": "^7.0.1", 122 | "hex-and-rgba": "^1.3.5", 123 | "husky": "^0.14.3", 124 | "jszip": "^3.1.5", 125 | "kactus-cli": "^0.5.4", 126 | "lint-staged": "^4.2.3", 127 | "md-2-json": "^1.0.5", 128 | "moment": "^2.22.2", 129 | "node-fetch": "^2.3.0", 130 | "path": "^0.12.7", 131 | "polished": "^2.3.1", 132 | "pre-commit": "^1.2.2", 133 | "prettier": "^1.14.3", 134 | "rc-queue-anim": "^1.6.8", 135 | "redbox-react": "^1.3.2", 136 | "roadhog": "2.5.0-beta.1", 137 | "sketch-polyfill-fetch": "^0.4.5", 138 | "sketch-utils": "^0.2.9", 139 | "string_decoder": "^1.2.0", 140 | "styled-components": "^4.0.2" 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /panel/components/ButtonGroup.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { Component } from 'react'; 3 | import { connect } from 'dva'; 4 | 5 | /// ///////////////////////////////////////////// 6 | // styled 7 | /// ///////////////////////////////////////////// 8 | 9 | const Group = styled.div` 10 | position: fixed; 11 | padding: 1rem; 12 | bottom: 0; 13 | left: 48px; 14 | display: flex; 15 | width: calc(100% - 48px); 16 | button { 17 | flex: 1; 18 | } 19 | button + button { 20 | margin-left: 0.5rem; 21 | } 22 | `; 23 | 24 | /// ///////////////////////////////////////////// 25 | // connect 26 | /// ///////////////////////////////////////////// 27 | 28 | const State = state => { 29 | return { 30 | theme: state.store.theme, 31 | }; 32 | }; 33 | 34 | /// ///////////////////////////////////////////// 35 | // component 36 | /// ///////////////////////////////////////////// 37 | 38 | class ButtonGroup extends Component { 39 | render() { 40 | const style = { 41 | background: this.props.theme === 'black' ? '#222' : '#f5f5f5', 42 | }; 43 | return {this.props.children}; 44 | } 45 | } 46 | 47 | export default connect(State)(ButtonGroup); 48 | -------------------------------------------------------------------------------- /panel/components/Cell.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import { Icon } from 'antd'; 3 | /// ///////////////////////////////////////////// 4 | // styled 5 | /// ///////////////////////////////////////////// 6 | 7 | const Cell = styled.div` 8 | margin-bottom: 0.5rem; 9 | display: flex; 10 | align-items: center; 11 | font-size: 0.7rem; 12 | border-radius: 0.2rem; 13 | cursor: pointer; 14 | transition: all 0.2s ease-out; 15 | &:active { 16 | transform: scale(0.95); 17 | } 18 | `; 19 | 20 | const Header = styled.div` 21 | width: 100%; 22 | margin-bottom: 0.5rem; 23 | font-weight: 600; 24 | cursor: pointer; 25 | `; 26 | 27 | const Dropdown = styled(Icon)` 28 | font-size: 0.7rem; 29 | opacity: 0.5; 30 | margin-right: 0.2rem; 31 | `; 32 | 33 | const Group = styled.div` 34 | margin-bottom: 1rem; 35 | overflow: hidden; 36 | `; 37 | 38 | Cell.Title = styled.div` 39 | padding: 0.2rem 0.4rem; 40 | border-radius: 2px; 41 | word-wrap: break-word; 42 | overflow: hidden; 43 | flex: 1; 44 | cursor: pointer; 45 | ${props => 46 | props.active 47 | ? css` 48 | background: rgba(100, 100, 100, 0.1); 49 | font-weight: 600; 50 | ` 51 | : null}; 52 | &:active { 53 | background: rgba(100, 100, 100, 0.1); 54 | font-weight: 600; 55 | } 56 | `; 57 | 58 | /// ///////////////////////////////////////////// 59 | // component 60 | /// ///////////////////////////////////////////// 61 | 62 | Cell.Group = ({ children, dropdown, active, ...other }) => { 63 | let style; 64 | if (dropdown) style = active ? null : { height: 0, marginBottom: 0 }; 65 | return ( 66 | 67 | {children} 68 | 69 | ); 70 | }; 71 | 72 | Cell.Header = ({ children, dropdown, active, ...other }) => { 73 | return ( 74 |
75 | {dropdown ? : null} 76 | {children} 77 |
78 | ); 79 | }; 80 | 81 | export default Cell; 82 | -------------------------------------------------------------------------------- /panel/components/Check.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { Icon } from 'antd'; 3 | /// ///////////////////////////////////////////// 4 | // styled 5 | /// ///////////////////////////////////////////// 6 | 7 | const Title = styled.div` 8 | width: 100%; 9 | height: 100vh; 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | flex-direction: column; 14 | `; 15 | 16 | const CheckIcon = styled(Icon)` 17 | font-size: 2rem; 18 | margin-bottom: 0.5rem; 19 | `; 20 | 21 | /// ///////////////////////////////////////////// 22 | // component 23 | /// ///////////////////////////////////////////// 24 | 25 | const Check = () => ( 26 | 27 | <CheckIcon type="sketch" /> 28 | 请保证阿里内网链接 29 | 30 | ); 31 | 32 | export default Check; 33 | -------------------------------------------------------------------------------- /panel/components/Close.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { Component } from 'react'; 3 | import { connect } from 'dva'; 4 | 5 | /// ///////////////////////////////////////////// 6 | // styled 7 | /// ///////////////////////////////////////////// 8 | 9 | const Btn = styled.div` 10 | position: fixed; 11 | bottom: 0; 12 | right: 0; 13 | width: 3rem; 14 | height: 2rem; 15 | line-height: 2rem; 16 | text-align: center; 17 | opacity: 0.2; 18 | transition: all 0.2s ease-out; 19 | cursor: pointer; 20 | text-align: center; 21 | &:hover { 22 | opacity: 1; 23 | } 24 | &:active { 25 | transform: scale(0.9); 26 | } 27 | `; 28 | 29 | /// ///////////////////////////////////////////// 30 | // connect 31 | /// ///////////////////////////////////////////// 32 | 33 | const State = () => ({}); 34 | 35 | const Dispatch = dispatch => ({ 36 | setConfig(obj) { 37 | dispatch({ type: `config/update`, payload: obj }); 38 | }, 39 | }); 40 | 41 | /// ///////////////////////////////////////////// 42 | // component 43 | /// ///////////////////////////////////////////// 44 | 45 | class Close extends Component { 46 | handleClose = name => { 47 | this.props.setConfig({ [name]: false }); 48 | window.postMessage('closePanel', null); 49 | }; 50 | 51 | render() { 52 | return ( 53 | this.handleClose(this.props.name)} /> 54 | ); 55 | } 56 | } 57 | 58 | export default connect( 59 | State, 60 | Dispatch 61 | )(Close); 62 | -------------------------------------------------------------------------------- /panel/components/Iconfont.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Icon = styled.div` 4 | font-size: 1.5rem; 5 | width: 100%; 6 | height: 3rem; 7 | line-height: 3rem; 8 | text-align: center; 9 | transition: all 0.1s ease-out; 10 | `; 11 | 12 | const More = styled.div` 13 | font-size: 1.5rem; 14 | position: absolute; 15 | let: 0; 16 | top: 0; 17 | width: 100%; 18 | height: 3rem; 19 | line-height: 3rem; 20 | text-align: center; 21 | opacity: 0.5; 22 | `; 23 | 24 | const Title = styled.div` 25 | text-align: center; 26 | margin-top: -0.5rem; 27 | font-size: 0.7rem; 28 | font-weight: 500; 29 | opacity: 0.7; 30 | `; 31 | 32 | const View = styled.div` 33 | margin-top: 0.2rem; 34 | width: 100%; 35 | display: flex; 36 | flex-direction: column; 37 | align-items: center; 38 | cursor: pointer; 39 | &:active { 40 | ${Icon} { 41 | transform: scale(0.9); 42 | } 43 | } 44 | `; 45 | 46 | export default ({ type, more, title = '标题', ...other }) => { 47 | return ( 48 | 49 | 50 | {more ? : null} 51 | 52 | {title} 53 | 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /panel/components/ListView.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { Component } from 'react'; 3 | import { connect } from 'dva'; 4 | 5 | /// ///////////////////////////////////////////// 6 | // styled 7 | /// ///////////////////////////////////////////// 8 | 9 | const List = styled.div` 10 | padding: 1rem; 11 | overflow: hidden; 12 | overflow-y: auto; 13 | `; 14 | 15 | /// ///////////////////////////////////////////// 16 | // connect 17 | /// ///////////////////////////////////////////// 18 | 19 | const State = state => { 20 | return { 21 | theme: state.store.theme, 22 | }; 23 | }; 24 | 25 | /// ///////////////////////////////////////////// 26 | // component 27 | /// ///////////////////////////////////////////// 28 | 29 | class ListView extends Component { 30 | render() { 31 | const { width = 'auto', border, theme, flex, grey, children } = this.props; 32 | const style = { 33 | width: width, 34 | }; 35 | if (border) 36 | style.borderRight = theme === 'black' ? '1px solid rgba(0, 0, 0, 0.1)' : '1px solid #eee'; 37 | 38 | if (flex) style.flex = 1; 39 | 40 | if (grey) style.background = '#f5f5f5'; 41 | return {children}; 42 | } 43 | } 44 | 45 | export default connect(State)(ListView); 46 | -------------------------------------------------------------------------------- /panel/components/Loading.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { Spin } from 'antd'; 3 | /// ///////////////////////////////////////////// 4 | // styled 5 | /// ///////////////////////////////////////////// 6 | 7 | const View = styled.div` 8 | width: 100%; 9 | height: 100vh; 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | `; 14 | 15 | /// ///////////////////////////////////////////// 16 | // component 17 | /// ///////////////////////////////////////////// 18 | 19 | const Loading = () => ( 20 | 21 | 22 | 23 | ); 24 | 25 | export default Loading; 26 | -------------------------------------------------------------------------------- /panel/components/Title.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import { Component } from 'react'; 3 | import { connect } from 'dva'; 4 | 5 | /// ///////////////////////////////////////////// 6 | // styled 7 | /// ///////////////////////////////////////////// 8 | 9 | const Text = styled.div` 10 | width: 100%; 11 | height: 48px; 12 | line-height: 48px; 13 | display: flex; 14 | font-size: 1.2rem; 15 | align-items: center; 16 | padding: 0 1rem; 17 | font-weight: bolder; 18 | color: rgba(100, 100, 100, 0.4); 19 | `; 20 | 21 | const Switch = styled.span` 22 | display: inline-block; 23 | margin-right: 1rem; 24 | cursor: pointer; 25 | ${props => 26 | props.active 27 | ? css` 28 | border-bottom: 3px solid #2a72ff; 29 | color: rgba(100, 100, 100, 1); 30 | ` 31 | : null} 32 | `; 33 | 34 | /// ///////////////////////////////////////////// 35 | // connect 36 | /// ///////////////////////////////////////////// 37 | 38 | const State = state => { 39 | return { 40 | theme: state.store.theme, 41 | }; 42 | }; 43 | 44 | /// ///////////////////////////////////////////// 45 | // component 46 | /// ///////////////////////////////////////////// 47 | 48 | class TitleClass extends Component { 49 | render() { 50 | const style = { 51 | boxShadow: 52 | this.props.theme === 'black' 53 | ? '0 4px 12px rgba(0, 0, 0, 0.2)' 54 | : '0 4px 8px rgba(0, 0, 0, 0.05)', 55 | }; 56 | return ( 57 | 58 | {this.props.children} 59 | 60 | ); 61 | } 62 | } 63 | 64 | const Title = connect(State)(TitleClass); 65 | Title.Switch = Switch; 66 | export default Title; 67 | -------------------------------------------------------------------------------- /panel/components/View.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import QueueAnim from 'rc-queue-anim'; 3 | 4 | /// ///////////////////////////////////////////// 5 | // styled 6 | /// ///////////////////////////////////////////// 7 | 8 | const View = styled(QueueAnim)` 9 | width: 368px; 10 | height: calc(100vh - 50px); 11 | overflow: hidden; 12 | `; 13 | 14 | /// ///////////////////////////////////////////// 15 | // component 16 | /// ///////////////////////////////////////////// 17 | 18 | export default ({ children, width = 48, padding, inner }) => { 19 | const style = { 20 | width: width + 'px', 21 | padding: padding ? '1rem' : 0, 22 | display: inner ? 'flex' : 'block', 23 | }; 24 | return ( 25 | 26 | {children} 27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /panel/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Iconfont } from './Iconfont'; 2 | export { default as Title } from './Title'; 3 | export { default as Close } from './Close'; 4 | export { default as View } from './View'; 5 | export { default as ListView } from './ListView'; 6 | export { default as Cell } from './Cell'; 7 | export { default as ButtonGroup } from './ButtonGroup'; 8 | export { default as Loading } from './Loading'; 9 | export { default as Check } from './Check'; 10 | -------------------------------------------------------------------------------- /panel/data/color.json: -------------------------------------------------------------------------------- 1 | [{"key":1,"name":"灰阶","colors":[{"key":1,"name":"灰0","type":"Color","color":"#333"},{"key":6,"name":"灰+5","type":"Color","color":"#f7f7f7"},{"key":5,"name":"灰+4","type":"Color","color":"#eee"},{"key":3,"name":"灰+2","type":"Color","color":"#999"},{"key":2,"name":"灰+1","type":"Color","color":"#666"},{"key":4,"name":"灰+3","type":"Color","color":"#ccc"}]},{"key":11,"name":"尊享蓝","colors":[{"key":5,"name":"尊享蓝+4","type":"Color","color":"#c9cee0"},{"key":2,"name":"尊享蓝+1","type":"Color","color":"#4753a2"},{"key":0,"name":"尊享蓝-1","type":"Color","color":"#0a0f22"},{"key":6,"name":"尊享蓝-渐变-1","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#0c155e","position":0},{"color":"#090e22","position":1}]}},{"key":3,"name":"尊享蓝+2","type":"Color","color":"#8c99cf"},{"key":1,"name":"尊享蓝0","type":"Color","color":"#0b1358"},{"key":4,"name":"尊享蓝+3","type":"Color","color":"#b4bde0"},{"key":7,"name":"尊享蓝-渐变0","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#162d8d","position":0},{"color":"#0c1566","position":1}]}}]},{"key":10,"name":"图标色","colors":[{"key":1,"name":"图标红-主","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#ff7028","position":0},{"color":"#ff3b28","position":1}]}},{"key":2,"name":"图标橙-副","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#ffa0294c","position":0},{"color":"#ff6619","position":1}]}},{"key":6,"name":"图标黄-副","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#ffb5204c","position":0},{"color":"#ffa322","position":1}]}},{"key":10,"name":"图标蓝-副","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#26a5ff4c","position":0},{"color":"#266eff","position":1}]}},{"key":0,"name":"图标白","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#fff","position":0},{"color":"#ffffffb2","position":1}]}},{"key":11,"name":"图标紫-主","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#a375ff","position":0},{"color":"#8c75ff","position":1}]}},{"key":5,"name":" 图标黄-主","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#ffb521","position":0},{"color":"#ffa322","position":1}]}},{"key":8,"name":"图标青-副","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#2bcaff4c","position":0},{"color":"#2bacff","position":1}]}},{"key":7,"name":"图标青-主","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#2bcaff","position":0},{"color":"#2bacff","position":1}]}},{"key":4,"name":"图标橙-副","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#ff70294c","position":0},{"color":"#ff3b28","position":1}]}},{"key":12,"name":"图标紫-副","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#a274ff4c","position":0},{"color":"#8c75ff","position":1}]}},{"key":3,"name":"图标红-主","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#ff8000","position":0},{"color":"#ff4f00","position":1}]}},{"key":9,"name":"图标蓝-主","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#26a4ff","position":0},{"color":"#266eff","position":1}]}}]},{"key":5,"name":"绿","colors":[{"key":2,"name":"绿0","type":"Color","color":"#24a69b"},{"key":5,"name":"绿+3","type":"Color","color":"#a7dbd7"},{"key":6,"name":"绿+4","type":"Color","color":"#d3edeb"},{"key":4,"name":"绿+2","type":"Color","color":"#7bc9c3"},{"key":3,"name":"绿+1","type":"Color","color":"#4fb7af"},{"key":1,"name":"绿-1","type":"Color","color":"#1b837c"}]},{"key":9,"name":"渐变","colors":[{"key":3,"name":"黄","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#ffd91a","position":0},{"color":"#ffb31a","position":1}]}},{"key":5,"name":"蓝","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#2693ff","position":0},{"color":"#266eff","position":1}]}},{"key":1,"name":"红","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#f25535","position":0},{"color":"#eb332f","position":1}]}},{"key":4,"name":"青","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#0cbeff","position":0},{"color":"#0c9eff","position":1}]}},{"key":2,"name":"橙","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#fc8116","position":0},{"color":"#ff5e0e","position":1}]}},{"key":6,"name":"紫","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#6a00ff","position":0},{"color":"#5800ff","position":1}]}}]},{"key":7,"name":"蓝","colors":[{"key":5,"name":"蓝+3","type":"Color","color":"#a8c5ff"},{"key":2,"name":"蓝0","type":"Color","color":"#266eff"},{"key":1,"name":"蓝-1","type":"Color","color":"#1d58cc"},{"key":4,"name":"蓝+2","type":"Color","color":"#7ca8ff"},{"key":6,"name":"蓝+4","type":"Color","color":"#d3e2ff"},{"key":3,"name":"蓝+1","type":"Color","color":"#518bff"}]},{"key":3,"name":"橙","colors":[{"key":2,"name":"橙0","type":"Color","color":"#ff6619"},{"key":6,"name":"橙+4","type":"Color","color":"#ffe3d6"},{"key":5,"name":"橙+3","type":"Color","color":"#ffc8ad"},{"key":1,"name":"橙-1","type":"Color","color":"#cc5f28"},{"key":4,"name":"橙+2","type":"Color","color":"#ffad84"},{"key":3,"name":"橙+1","type":"Color","color":"#ff925b"}]},{"key":0,"name":"平台色","colors":[{"key":3,"name":"分割线颜色","type":"Color","color":"#eee"},{"key":2,"name":"全局背景色","type":"Color","color":"#f7f7f7"},{"key":1,"name":"App品牌色","type":"Color","color":"#266eff"}]},{"key":6,"name":"青","colors":[{"key":5,"name":"青+3","type":"Color","color":"#99d6ff"},{"key":2,"name":"青0","type":"Color","color":"#0c9eff"},{"key":6,"name":"青+4","type":"Color","color":"#ccecff"},{"key":4,"name":"青+2","type":"Color","color":"#66c1ff"},{"key":1,"name":"青-1","type":"Color","color":"#0a7ecc"},{"key":3,"name":"青+1","type":"Color","color":"#3fb2ff"}]},{"key":12,"name":"尊享金","colors":[{"key":6,"name":"尊享金-渐变-1","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#a87952","position":0},{"color":"#6e4326","position":1}]}},{"key":5,"name":"尊享金+3","type":"Color","color":"#eddcc9"},{"key":4,"name":"尊享金+2","type":"Color","color":"#edd3b5"},{"key":1,"name":"尊享金-1","type":"Color","color":"#a87952"},{"key":2,"name":"尊享金0","type":"Color","color":"#e3b07f"},{"key":0,"name":"尊享金-2","type":"Color","color":"#6e4326"},{"key":7,"name":"尊享金-渐变0","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#edd3b5","position":0},{"color":"#e3b07f","position":1}]}},{"key":3,"name":"尊享金+1","type":"Color","color":"#e8c199"}]},{"key":2,"name":"红","colors":[{"key":1,"name":"红-1","type":"Color","color":"#c1442a"},{"key":4,"name":"红+2","type":"Color","color":"#f79985"},{"key":3,"name":"红+1","type":"Color","color":"#f3775d"},{"key":5,"name":"红+3","type":"Color","color":"#f9bbae"},{"key":6,"name":"红+4","type":"Color","color":"#fcddd6"},{"key":2,"name":"红0","type":"Color","color":"#f25534"}]},{"key":8,"name":"紫","colors":[{"key":2,"name":"紫0","type":"Color","color":"#5800ff"},{"key":6,"name":"紫+4","type":"Color","color":"#dcf"},{"key":3,"name":"紫+1","type":"Color","color":"#7933ff"},{"key":5,"name":"紫+3","type":"Color","color":"#bc99ff"},{"key":4,"name":"紫+2","type":"Color","color":"#9b66ff"},{"key":1,"name":"紫-1","type":"Color","color":"#4700cc"}]},{"key":4,"name":"黄","colors":[{"key":6,"name":"黄+4","type":"Color","color":"#fec"},{"key":3,"name":"黄+1","type":"Color","color":"#fb3"},{"key":4,"name":"黄+2","type":"Color","color":"#fc6"},{"key":1,"name":"黄-1","type":"Color","color":"#c80"},{"key":2,"name":"黄0","type":"Color","color":"#fa0"},{"key":5,"name":"黄+3","type":"Color","color":"#fd9"}]}] -------------------------------------------------------------------------------- /panel/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #root { 4 | height: 100%; 5 | overflow: hidden; 6 | user-select: none; 7 | background: transparent; 8 | } 9 | 10 | body { 11 | -webkit-font-smoothing: antialiased; 12 | -moz-osx-font-smoothing: grayscale; 13 | -ms-text-size-adjust: 100%; 14 | -webkit-text-size-adjust: 100%; 15 | } 16 | 17 | *:focus { 18 | outline: none; 19 | } 20 | 21 | * { 22 | -webkit-font-smoothing: antialiased; 23 | } 24 | 25 | div { 26 | box-sizing: border-box; 27 | position: relative; 28 | } 29 | 30 | ::-webkit-scrollbar { 31 | background: #f8f8f8; 32 | height: 0.4rem; 33 | width: 0; 34 | } 35 | 36 | ::-webkit-scrollbar-track { 37 | background: transparent; 38 | border-radius: 0; 39 | box-shadow: none; 40 | } 41 | 42 | ::-webkit-scrollbar-thumb { 43 | background-color: #ccc; 44 | border-radius: 0; 45 | box-shadow: none; 46 | transition: background-color 0.5s ease; 47 | } 48 | 49 | ::-webkit-scrollbar-thumb:hover { 50 | background-color: #000; 51 | } -------------------------------------------------------------------------------- /panel/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | Anto 8 | 9 | 10 | 11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /panel/index.js: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | import dva from 'dva'; 3 | import createLoading from 'dva-loading'; 4 | import './index.css'; 5 | 6 | // 1. Initialize 7 | const app = dva({ 8 | onError(e) { 9 | message.error(e.message, 3); 10 | }, 11 | }); 12 | 13 | // 2. Models 14 | app.model(require('./models/check').default); 15 | app.model(require('./models/username').default); 16 | app.model(require('./models/store').default); 17 | app.model(require('./models/config').default); 18 | app.model(require('./models/symbols').default); 19 | app.model(require('./models/colors').default); 20 | app.model(require('./models/docs').default); 21 | 22 | // 2. Plugins 23 | app.use(createLoading()); 24 | 25 | // 3. Router 26 | app.router(require('./router').default); 27 | 28 | // 4. Start 29 | app.start('#root'); 30 | 31 | // 5. Func 32 | document.addEventListener('contextmenu', e => e.preventDefault()); 33 | -------------------------------------------------------------------------------- /panel/models/check.js: -------------------------------------------------------------------------------- 1 | import { request } from '../utils/request'; 2 | 3 | export default { 4 | namespace: 'check', 5 | state: false, 6 | reducers: { 7 | save(state, action) { 8 | state = action.payload; 9 | return state; 10 | }, 11 | }, 12 | effects: { 13 | *get(action, { call, put }) { 14 | let check = false; 15 | try { 16 | const result = yield call(() => request('http://anto.inc.alipay.net/static/check.json')); 17 | console.log(result); 18 | if (result.data && result.data.check) check = true; 19 | } catch (e) { 20 | console.log(e); 21 | } 22 | yield put({ 23 | type: 'save', 24 | payload: check, 25 | }); 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /panel/models/colors.js: -------------------------------------------------------------------------------- 1 | import { request } from '../utils/request'; 2 | 3 | export default { 4 | namespace: 'colors', 5 | state: [], 6 | reducers: { 7 | save(state, action) { 8 | state = action.payload; 9 | return state; 10 | }, 11 | }, 12 | effects: { 13 | *get(action, { call, put, select }) { 14 | const check = yield select(state => state.check); 15 | let url = 16 | 'https://raw.githubusercontent.com/canisminor1990/anto-cloud/master/public/colors/data.json'; 17 | if (check) url = 'http://anto.inc.alipay.net/static/colors.json'; 18 | const result = yield call(() => request(url)); 19 | yield put({ 20 | type: 'save', 21 | payload: result.data, 22 | }); 23 | }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /panel/models/config.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export default { 4 | namespace: 'config', 5 | 6 | state: { 7 | symbol: false, 8 | color: false, 9 | line: false, 10 | note: false, 11 | layer: false, 12 | plate: false, 13 | word: false, 14 | dev: false, 15 | setting: false, 16 | }, 17 | 18 | reducers: { 19 | updateSuccess(state, action) { 20 | const payload = action.payload; 21 | _.forEach(payload, (value, key) => { 22 | if (value) { 23 | _.forEach(state, (v, k) => { 24 | if (state[k] !== key) state[k] = false; 25 | }); 26 | } 27 | }); 28 | return { ...state, ...payload }; 29 | }, 30 | }, 31 | 32 | effects: { 33 | *update(action, { put }) { 34 | const payload = action.payload; 35 | console.log('update', payload); 36 | yield put({ type: 'updateSuccess', payload }); 37 | }, 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /panel/models/docs.js: -------------------------------------------------------------------------------- 1 | import { request } from '../utils/request'; 2 | 3 | export default { 4 | namespace: 'docs', 5 | state: [], 6 | reducers: { 7 | save(state, action) { 8 | state = action.payload; 9 | return state; 10 | }, 11 | }, 12 | effects: { 13 | *get(action, { call, put }) { 14 | const result = yield call(() => request(`http://anto.inc.alipay.net/static/docs.json`)); 15 | yield put({ 16 | type: 'save', 17 | payload: result.data, 18 | }); 19 | }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /panel/models/store.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | const local = _.defaults(JSON.parse(localStorage.getItem('store')), { 4 | theme: 'black', 5 | mode: '交互', 6 | name: '花名', 7 | devMode: false, 8 | }); 9 | 10 | export default { 11 | namespace: 'store', 12 | 13 | state: { 14 | ...local, 15 | }, 16 | 17 | reducers: { 18 | updateSuccess(state, action) { 19 | const payload = action.payload; 20 | const newLocal = { ...state, ...payload }; 21 | localStorage.setItem('store', JSON.stringify(newLocal)); 22 | return newLocal; 23 | }, 24 | }, 25 | 26 | effects: { 27 | *update(action, { put }) { 28 | const payload = action.payload; 29 | console.log('update', payload); 30 | yield put({ type: 'updateSuccess', payload }); 31 | }, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /panel/models/symbols.js: -------------------------------------------------------------------------------- 1 | import { request } from '../utils/request'; 2 | 3 | export default { 4 | namespace: 'symbols', 5 | state: {}, 6 | reducers: { 7 | save(state, action) { 8 | state = action.payload; 9 | return state; 10 | }, 11 | }, 12 | effects: { 13 | *get(action, { call, put }) { 14 | const tabs = yield call(() => request('http://100.88.232.163/static/tabs.json')); 15 | const tabsData = tabs.data; 16 | let data = {}; 17 | for (let i = 0; i < tabsData.length; i++) { 18 | const libdata = yield call(() => 19 | request(`http://100.88.232.163/static/${tabsData[i].dirname}/data.json`) 20 | ); 21 | data[tabsData[i].dirname] = { 22 | data: libdata.data, 23 | ...tabsData[i], 24 | }; 25 | } 26 | yield put({ 27 | type: 'save', 28 | payload: data, 29 | }); 30 | }, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /panel/models/username.js: -------------------------------------------------------------------------------- 1 | import { request } from '../utils/request'; 2 | import _ from 'lodash'; 3 | 4 | export default { 5 | namespace: 'username', 6 | state: [], 7 | reducers: { 8 | save(state, action) { 9 | state = action.payload; 10 | return state; 11 | }, 12 | }, 13 | effects: { 14 | *get(action, { call, put }) { 15 | const name = action.payload; 16 | const names = []; 17 | try { 18 | const result = yield call(() => 19 | request(`http://anto.inc.alipay.net/api/user/search?keyword=${name}`) 20 | ); 21 | _.forEach(result.data.result, value => { 22 | if (value.nick && value.nick !== '') names.push(value.nick); 23 | }); 24 | } catch (e) { 25 | console.log(e); 26 | } 27 | yield put({ 28 | type: 'save', 29 | payload: names, 30 | }); 31 | }, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /panel/router.js: -------------------------------------------------------------------------------- 1 | import { Route, Router } from 'dva/router'; 2 | import Panel from './routes'; 3 | 4 | export default ({ app, history }) => { 5 | history.listen(() => window.scrollTo(0, 0)); 6 | return ( 7 | 8 | 9 | 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /panel/routes/Colors.js: -------------------------------------------------------------------------------- 1 | import { hsl } from 'polished'; 2 | import { Component } from 'react'; 3 | import styled from 'styled-components'; 4 | import { connect } from 'dva'; 5 | import _ from 'lodash'; 6 | import { Slider, Switch } from 'antd'; 7 | import { Title, View, Close, Cell, ButtonGroup, Check, Loading } from '../components'; 8 | import { PostMessage } from '../utils/PostMessage'; 9 | 10 | /// ///////////////////////////////////////////// 11 | // styled 12 | /// ///////////////////////////////////////////// 13 | 14 | const Panel = styled.div` 15 | padding: 1rem; 16 | width: 100%; 17 | height: calc(100% - 50px); 18 | overflow-y: auto; 19 | `; 20 | 21 | const Icon = styled.div` 22 | width: 2rem; 23 | height: 2rem; 24 | border-radius: 2px; 25 | background: #333; 26 | margin-right: 0.5rem; 27 | `; 28 | 29 | const Sub = styled.div` 30 | display: flex; 31 | align-items: center; 32 | `; 33 | const Point = styled.div` 34 | width: 0.5rem; 35 | height: 0.5rem; 36 | border-radius: 50%; 37 | margin-right: 0.25rem; 38 | `; 39 | const Desc = styled.span` 40 | opacity: 0.5; 41 | + ${Point} { 42 | margin-left: 0.5rem; 43 | } 44 | `; 45 | 46 | const Flex = styled.div` 47 | display: flex; 48 | margin-bottom: 0.5rem; 49 | align-items: center; 50 | `; 51 | 52 | const SliderBox = styled.div` 53 | width: 100%; 54 | display: flex; 55 | height: 4rem; 56 | align-items: center; 57 | margin-bottom: 14rem; 58 | `; 59 | 60 | const ColorBlock = styled.div` 61 | width: 2rem; 62 | height: 2rem; 63 | background: ${props => hsl(props.h, props.s, props.l)}; 64 | cursor: pointer; 65 | color: ${props => hsl(props.h, props.s, props.l - 0.5 > 0 ? props.l - 0.5 : 0)}; 66 | display: flex; 67 | align-items: center; 68 | justify-content: center; 69 | font-size: 0.7rem; 70 | &:active { 71 | box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); 72 | transform: scale(1.2); 73 | z-index: 10; 74 | border-radius: 2px; 75 | } 76 | `; 77 | 78 | const Group = styled.div` 79 | width: 8rem; 80 | height: 8rem; 81 | position: fixed; 82 | top: 7rem; 83 | left: 2.8rem; 84 | transform: rotate(${props => props.deg}deg); 85 | `; 86 | 87 | /// ///////////////////////////////////////////// 88 | // connect 89 | /// ///////////////////////////////////////////// 90 | const State = state => { 91 | return { 92 | check: state.check, 93 | loading: state.loading.global || state.colors.length === 0, 94 | colors: state.colors || {}, 95 | }; 96 | }; 97 | 98 | const Dispatch = dispatch => ({ 99 | getColors() { 100 | dispatch({ type: `colors/get` }); 101 | }, 102 | }); 103 | 104 | /// ///////////////////////////////////////////// 105 | // component 106 | /// ///////////////////////////////////////////// 107 | 108 | class Colors extends Component { 109 | state = { 110 | tab: '色板', 111 | activeColor: null, 112 | border: false, 113 | count: 8, 114 | header: {}, 115 | }; 116 | 117 | localStorageName = 'dropdown-color'; 118 | 119 | componentDidMount() { 120 | this.props.getColors(); 121 | let state = localStorage.getItem(this.localStorageName); 122 | if (state) { 123 | this.setState({ header: JSON.parse(state) }); 124 | } else { 125 | localStorage.setItem(this.localStorageName, '{}'); 126 | } 127 | } 128 | 129 | SwitchTitle = ({ name }) => ( 130 | this.setState({ tab: name })}> 131 | {name} 132 | 133 | ); 134 | 135 | mapColor = group => { 136 | const List = []; 137 | _.forEach(group, (data, key) => { 138 | const desc = ( 139 | 140 | {data.desc} 141 | 142 | ); 143 | List.push( 144 | this.handleClick(data.color)}> 145 | 146 | 147 | {data.name} 148 | {desc} 149 | 150 | 151 | ); 152 | }); 153 | return List; 154 | }; 155 | 156 | ColorCircle = () => { 157 | const List = []; 158 | const Groups = []; 159 | 160 | const alpha = (1 - 0.575) / 5; 161 | for (let i = 0; i < this.state.count; i++) { 162 | let Colors = []; 163 | let angle = 220 + (360 / this.state.count) * i; 164 | if (angle > 360) angle = angle - 360; 165 | List.push( 166 | 167 | 168 | 169 | ); 170 | for (let i = 6; i > 0; i--) { 171 | const l = 1 - alpha * i; 172 | Colors.push( 173 | this.handleClick(hsl(angle, 1, l))} 179 | > 180 | {5 - i > 0 ? `+${5 - i}` : 5 - i} 181 | 182 | ); 183 | } 184 | const hslColor = hsl(angle, 1, 0.575); 185 | Groups.push( 186 | 187 | 188 | {hslColor} 189 | 190 | ); 191 | Groups.push({Colors}); 192 | } 193 | 194 | return ( 195 |
196 | 197 | this.setState({ count: e })} 203 | /> 204 | 205 | {List} 206 | 色彩列表 207 | {Groups} 208 |
209 | ); 210 | }; 211 | 212 | ColorView = () => { 213 | const List = []; 214 | _.forEach(this.props.colors, (group, key) => { 215 | const active = !this.state.header[key]; 216 | List.push( 217 |
218 | this.handleHeader(key)}> 219 | {key} 220 | 221 | 222 | {this.mapColor(group)} 223 | 224 |
225 | ); 226 | }); 227 | return List; 228 | }; 229 | CheckView = () => (this.props.loading ? : ); 230 | 231 | render() { 232 | return ( 233 | <> 234 | 235 | <this.SwitchTitle name="色板" /> 236 | <this.SwitchTitle name="色轮" /> 237 | 238 | 239 | 240 | {this.state.tab === '色板' ? : } 241 | 242 | 243 |
244 | 描边: 245 | this.setState({ border: e })} 249 | /> 250 |
251 |
252 | 253 |
254 | 255 | ); 256 | } 257 | 258 | handleHeader = name => { 259 | const newState = this.state.header; 260 | newState[name] = !this.state.header[name]; 261 | this.setState({ header: newState }); 262 | localStorage.setItem(this.localStorageName, JSON.stringify(newState)); 263 | }; 264 | 265 | handleClick = color => { 266 | const data = { border: this.state.border, color }; 267 | PostMessage('handleColor', JSON.stringify(data)); 268 | }; 269 | } 270 | 271 | export default connect( 272 | State, 273 | Dispatch 274 | )(Colors); 275 | -------------------------------------------------------------------------------- /panel/routes/Dev.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { Iconfont, Title, Close, View } from '../components'; 3 | import { PostMessage } from '../utils/PostMessage'; 4 | 5 | /// ///////////////////////////////////////////// 6 | // component 7 | /// ///////////////////////////////////////////// 8 | 9 | class Dev extends Component { 10 | render() { 11 | return ( 12 | <> 13 | ⚙️ 14 | 15 | PostMessage('devNumber', null)} 20 | /> 21 | PostMessage('devSymbol', null)} 26 | /> 27 | PostMessage('devColor', null)} 32 | /> 33 | PostMessage('devTest', null)} 38 | /> 39 | 40 | 41 | 42 | ); 43 | } 44 | } 45 | 46 | export default Dev; 47 | -------------------------------------------------------------------------------- /panel/routes/Docs.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import styled from 'styled-components'; 3 | import { Input } from 'antd'; 4 | import { connect } from 'dva'; 5 | import { Title, View, Close, Cell, Check, Loading } from '../components'; 6 | import _ from 'lodash'; 7 | import { PostMessage } from '../utils/PostMessage'; 8 | import { CopyToClipboard } from 'react-copy-to-clipboard'; 9 | 10 | const Search = Input.Search; 11 | /// ///////////////////////////////////////////// 12 | // styled 13 | /// ///////////////////////////////////////////// 14 | 15 | const Panel = styled.div` 16 | padding: 1rem; 17 | width: 9rem; 18 | height: calc(100% - 50px); 19 | overflow-y: auto; 20 | `; 21 | 22 | const SearchBlock = styled.div` 23 | transform: scale(0.9); 24 | position: fixed; 25 | top: -1px; 26 | right: 0; 27 | width: 15rem; 28 | `; 29 | 30 | const LibraryView = styled.div` 31 | height: 100%; 32 | flex: 1; 33 | padding: 1rem; 34 | background: #eee; 35 | overflow-y: auto; 36 | `; 37 | 38 | const LibGroup = styled.div` 39 | margin-bottom: 2rem; 40 | `; 41 | 42 | const LibHeader = styled.div` 43 | color: #333; 44 | font-size: 1.1rem; 45 | font-weight: 600; 46 | padding-bottom: 0.5rem; 47 | border-bottom: 3px solid #ddd; 48 | `; 49 | 50 | const LibBlock = styled.div` 51 | margin-bottom: 0.5rem; 52 | 53 | padding: 1rem 0; 54 | padding-bottom: 1.5rem; 55 | border-bottom: 1px dashed #ddd; 56 | `; 57 | 58 | const LibTitle = styled.span` 59 | display: block; 60 | font-weight: 600; 61 | color: #2a72ff; 62 | margin-bottom: 0.5rem; 63 | height: 1.5rem; 64 | border-radius: 0.2rem; 65 | line-height: 1.5rem; 66 | padding: 0 0.4rem; 67 | margin-right: 0.3rem; 68 | margin-bottom: 0.3rem; 69 | cursor: pointer; 70 | background: rgba(255, 255, 255, 0.5); 71 | transition: all ease 0.2s; 72 | &:hover { 73 | background: #2a72ff; 74 | color: #fff; 75 | } 76 | &:active { 77 | transform: scale(0.95); 78 | } 79 | `; 80 | 81 | const LibTitleBlock = styled.div` 82 | display: flex; 83 | flex-wrap: wrap; 84 | margin-bottom: 0.5rem; 85 | `; 86 | 87 | const LibContent = styled.div` 88 | display: flex; 89 | flex-wrap: wrap; 90 | margin-top: 0.5rem; 91 | `; 92 | 93 | const LibDesc = styled.div` 94 | font-size: 0.7rem; 95 | color: #666; 96 | text-align: justify; 97 | margin-bottom: 0.3rem; 98 | `; 99 | 100 | const LibTag = styled.span` 101 | display: block; 102 | font-size: 0.8rem; 103 | color: #fff; 104 | border-radius: 0.2rem; 105 | line-height: 1.2rem; 106 | padding: 0 0.3rem; 107 | margin-right: 0.2rem; 108 | margin-bottom: 0.2rem; 109 | `; 110 | 111 | const LibTrue = styled(LibTag)` 112 | background: #24a69b; 113 | cursor: pointer; 114 | transition: all ease 0.2s; 115 | &:hover { 116 | font-weight: 600; 117 | } 118 | &:active { 119 | transform: scale(0.95); 120 | } 121 | `; 122 | 123 | const LibWrong = styled(LibTag)` 124 | background: #f25535; 125 | `; 126 | 127 | /// ///////////////////////////////////////////// 128 | // connect 129 | /// ///////////////////////////////////////////// 130 | const State = state => { 131 | return { 132 | check: state.check, 133 | loading: state.loading.global || state.docs.length === 0, 134 | docs: state.docs, 135 | }; 136 | }; 137 | 138 | const Dispatch = dispatch => ({ 139 | getDocs() { 140 | dispatch({ type: `docs/get` }); 141 | }, 142 | }); 143 | 144 | /// ///////////////////////////////////////////// 145 | // component 146 | /// ///////////////////////////////////////////// 147 | 148 | class Docs extends Component { 149 | state = { 150 | activeRoot: 0, 151 | activeGroup: 0, 152 | search: false, 153 | keywords: null, 154 | header: {}, 155 | }; 156 | 157 | localStorageName = 'dropdown-word'; 158 | 159 | componentDidMount() { 160 | this.props.getDocs(); 161 | let state = localStorage.getItem(this.localStorageName); 162 | if (state) { 163 | this.setState({ header: JSON.parse(state) }); 164 | } else { 165 | localStorage.setItem(this.localStorageName, '{}'); 166 | } 167 | } 168 | 169 | mapData = (data, index) => { 170 | const active = !this.state.header[data.title]; 171 | return ( 172 |
173 | this.handleHeader(data.title)}> 174 | {data.title} 175 | 176 | 177 | {data.group.map((data, i) => this.mapGroup(data, i, index))} 178 | 179 |
180 | ); 181 | }; 182 | 183 | mapGroup = (data, indexGroup, indexRoot) => { 184 | const active = this.state.activeRoot === indexRoot && this.state.activeGroup === indexGroup; 185 | return ( 186 | this.handleGroup(indexRoot, indexGroup)} 190 | > 191 | {indexGroup + 1}.{data.title} 192 | 193 | ); 194 | }; 195 | 196 | mapLib = (data, index) => { 197 | return ( 198 | 199 | 200 | {this.state.activeGroup + 1}.{index + 1} {data.title} 201 | 202 | {data.group.map(this.mapLibGroup)} 203 | 204 | ); 205 | }; 206 | 207 | mapLibGroup = (data, index) => { 208 | return ( 209 | 210 | 211 | {data.title.map((t, i) => ( 212 | 213 | this.handleCopy(t)}>{t} 214 | 215 | ))} 216 | 217 | {data.desc.split(/\n/g).map((t, i) => ( 218 | {t} 219 | ))} 220 | 221 | 222 | {data.true && data.true.length > 0 223 | ? data.true.map((t, i) => ( 224 | 225 | this.handleCopy(t)}>{t} 226 | 227 | )) 228 | : null} 229 | 230 | 231 | 232 | {data.wrong && data.wrong.length > 0 233 | ? data.wrong.map((t, i) => {t}) 234 | : null} 235 | 236 | 237 | ); 238 | }; 239 | 240 | SearchResult = () => { 241 | let newData = []; 242 | _.forEach(this.props.docs, h1 => { 243 | _.forEach(h1.group, h2 => { 244 | _.forEach(h2.group, h3 => { 245 | newData = newData.concat(h3.group); 246 | }); 247 | }); 248 | }); 249 | 250 | const result1 = []; 251 | const result2 = []; 252 | const result3 = []; 253 | const result4 = []; 254 | 255 | _.forEach(newData, d => { 256 | if (JSON.stringify(d.title).indexOf(this.state.keywords) > -1) result1.push(d); 257 | if (d.true && JSON.stringify(d.true).indexOf(this.state.keywords) > -1) result2.push(d); 258 | if (d.wrong && JSON.stringify(d.wrong).indexOf(this.state.keywords) > -1) result3.push(d); 259 | if (d.desc.indexOf(this.state.keywords) > -1) result4.push(d); 260 | }); 261 | 262 | const result = _.uniq([].concat(result1, result2, result3, result4)); 263 | 264 | return ( 265 | 266 | {result.length > 0 ? result.map(this.mapLibGroup) : '对不起,未找到匹配结果...'} 267 | 268 | ); 269 | }; 270 | 271 | DocsView = () => ( 272 | <> 273 | {this.props.docs.map(this.mapData)} 274 | 275 | {this.state.search ? ( 276 | 277 | ) : ( 278 | this.props.docs[this.state.activeRoot].group[this.state.activeGroup].group.map( 279 | this.mapLib 280 | ) 281 | )} 282 | 283 | 284 | ); 285 | CheckView = () => (this.props.loading ? : ); 286 | 287 | render() { 288 | return ( 289 | <> 290 | 291 | 话术 292 | <SearchBlock> 293 | <Search placeholder="请输入关键字" onSearch={this.handleSearch} enterButton /> 294 | </SearchBlock> 295 | 296 | 297 | {this.props.check ? : } 298 | 299 | 300 | 301 | ); 302 | } 303 | 304 | handleHeader = name => { 305 | const newState = this.state.header; 306 | newState[name] = !this.state.header[name]; 307 | this.setState({ header: newState }); 308 | localStorage.setItem(this.localStorageName, JSON.stringify(newState)); 309 | }; 310 | 311 | handleGroup = (activeRoot, activeGroup) => { 312 | this.setState({ search: false, activeRoot, activeGroup }); 313 | }; 314 | 315 | handleSearch = value => { 316 | this.setState({ search: true, keywords: value }); 317 | }; 318 | 319 | handleCopy = value => { 320 | PostMessage('handleWord', value); 321 | }; 322 | } 323 | 324 | export default connect( 325 | State, 326 | Dispatch 327 | )(Docs); 328 | -------------------------------------------------------------------------------- /panel/routes/Layer.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { Iconfont, Title, Close, View } from '../components'; 3 | import { PostMessage } from '../utils/PostMessage'; 4 | 5 | /// ///////////////////////////////////////////// 6 | // component 7 | /// ///////////////////////////////////////////// 8 | 9 | class Layer extends Component { 10 | render() { 11 | return ( 12 | <> 13 | 14 | 15 | PostMessage('handleLayout', null)} 20 | /> 21 | PostMessage('handleSort', null)} 26 | /> 27 | PostMessage('handleTopLite', null)} 32 | /> 33 | PostMessage('handleBottomLite', null)} 38 | /> 39 | PostMessage('handleTop', null)} 44 | /> 45 | PostMessage('handleBottom', null)} 50 | /> 51 | PostMessage('handleHeight', null)} 56 | /> 57 | PostMessage('handleBlender', null)} 62 | /> 63 | 64 | 65 | 66 | ); 67 | } 68 | } 69 | 70 | export default Layer; 71 | -------------------------------------------------------------------------------- /panel/routes/Line.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { Iconfont, Title, Close, View } from '../components'; 3 | import { PostMessage } from '../utils/PostMessage'; 4 | 5 | /// ///////////////////////////////////////////// 6 | // component 7 | /// ///////////////////////////////////////////// 8 | 9 | class Line extends Component { 10 | render() { 11 | return ( 12 | <> 13 | 线 14 | 15 | PostMessage('handleLine', null)} 20 | /> 21 | PostMessage('handleChange', null)} 26 | /> 27 | PostMessage('handleDash', null)} 32 | /> 33 | PostMessage('setRound', null)} 38 | /> 39 | PostMessage('setIf', null)} 44 | /> 45 | 46 | 47 | 48 | ); 49 | } 50 | } 51 | 52 | export default Line; 53 | -------------------------------------------------------------------------------- /panel/routes/Note.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { Iconfont, Title, Close, View } from '../components'; 3 | import { PostMessage } from '../utils/PostMessage'; 4 | 5 | /// ///////////////////////////////////////////// 6 | // component 7 | /// ///////////////////////////////////////////// 8 | 9 | class Note extends Component { 10 | render() { 11 | return ( 12 | <> 13 | 14 | 15 | PostMessage('setHeader', null)} 20 | /> 21 | PostMessage('setSubHeader', null)} 26 | /> 27 | PostMessage('setText', null)} 32 | /> 33 | PostMessage('setBlock', null)} 38 | /> 39 | PostMessage('setList', null)} 44 | /> 45 | PostMessage('setUl', null)} 50 | /> 51 | PostMessage('setPoint', null)} 56 | /> 57 | PostMessage('setChangelog', null)} 62 | /> 63 | 64 | 65 | 66 | ); 67 | } 68 | } 69 | 70 | export default Note; 71 | -------------------------------------------------------------------------------- /panel/routes/Plate.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { Iconfont, Title, Close, View } from '../components'; 3 | import { PostMessage } from '../utils/PostMessage'; 4 | 5 | /// ///////////////////////////////////////////// 6 | // component 7 | /// ///////////////////////////////////////////// 8 | 9 | class Layer extends Component { 10 | render() { 11 | return ( 12 | <> 13 | 14 | 15 | PostMessage('handleIgnore', null)} 20 | /> 21 | PostMessage('handleTitle', null)} 26 | /> 27 | PostMessage('handlePlate', null)} 32 | /> 33 | PostMessage('handleExport', null)} 38 | /> 39 | 40 | 41 | 42 | ); 43 | } 44 | } 45 | 46 | export default Layer; 47 | -------------------------------------------------------------------------------- /panel/routes/Setting.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import styled from 'styled-components'; 3 | import { Form, Input, Button, Switch, AutoComplete } from 'antd'; 4 | import { connect } from 'dva'; 5 | import QueueAnim from 'rc-queue-anim'; 6 | import { Title, View, ButtonGroup } from '../components'; 7 | import { PostMessage } from '../utils/PostMessage'; 8 | 9 | const FormItem = Form.Item; 10 | 11 | /// ///////////////////////////////////////////// 12 | // styled 13 | /// ///////////////////////////////////////////// 14 | 15 | const Version = styled.div` 16 | font-weight: 600; 17 | font-size: 1.2rem; 18 | padding-bottom: 1rem; 19 | margin-bottom: 1rem; 20 | opacity: 0.8; 21 | span { 22 | font-weight: 400; 23 | font-size: 0.8rem; 24 | opacity: 0.5; 25 | } 26 | border-bottom: 1px solid rgba(100, 100, 100, 0.2); 27 | `; 28 | 29 | /// ///////////////////////////////////////////// 30 | // connect 31 | /// ///////////////////////////////////////////// 32 | 33 | const State = state => { 34 | return { 35 | username: state.username, 36 | store: state.store, 37 | ...state.store, 38 | ...state.config, 39 | }; 40 | }; 41 | 42 | const Dispatch = dispatch => ({ 43 | setConfig(obj) { 44 | dispatch({ type: `config/update`, payload: obj }); 45 | }, 46 | setStore(obj) { 47 | dispatch({ type: `store/update`, payload: obj }); 48 | }, 49 | gerUsers(name) { 50 | dispatch({ type: `username/get`, payload: name }); 51 | }, 52 | }); 53 | 54 | /// ///////////////////////////////////////////// 55 | // component 56 | /// ///////////////////////////////////////////// 57 | 58 | class Setting extends Component { 59 | state = { 60 | name: '', 61 | theme: 'black', 62 | title: 'low', 63 | devMode: false, 64 | }; 65 | 66 | version = localStorage.getItem('version'); 67 | 68 | componentDidMount() { 69 | this.setState(this.props.store); 70 | } 71 | 72 | handleSearch = value => { 73 | this.props.gerUsers(value); 74 | console.log(this.props.username); 75 | }; 76 | 77 | render() { 78 | return ( 79 | <> 80 | 设置 81 | 82 | 83 | 84 | ANTO ver{this.version ? this.version : '1.0.0'} 85 | 86 | 87 | this.handleChange(e, 'name')} 92 | placeholder="请输入花名" 93 | /> 94 | 95 | 96 | 102 | 103 | 104 | 110 | 111 | 112 | 118 | 119 | 120 | PostMessage('handleYuque', null)}>CanisMinor (倏昱) 121 | 122 | 123 | 124 | 130 | 133 | 134 | 135 | 136 | ); 137 | } 138 | 139 | handleChange = (e, key) => { 140 | this.setState({ [key]: e }); 141 | }; 142 | 143 | handleDev = bool => { 144 | this.setState({ devMode: bool }); 145 | }; 146 | 147 | handleTheme = bool => { 148 | this.setState({ theme: bool ? 'black' : 'white' }); 149 | }; 150 | 151 | handleTitle = bool => { 152 | this.setState({ title: bool ? 'strong' : 'low' }); 153 | }; 154 | 155 | handleClose = () => { 156 | this.props.setConfig({ setting: false }); 157 | PostMessage('closeSetting', null); 158 | }; 159 | 160 | handleSave = () => { 161 | this.props.setStore(this.state); 162 | this.props.setConfig({ setting: false }); 163 | PostMessage('closeSetting', this.state); 164 | }; 165 | } 166 | 167 | export default connect( 168 | State, 169 | Dispatch 170 | )(Setting); 171 | -------------------------------------------------------------------------------- /panel/routes/Symbols.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { connect } from 'dva'; 3 | import styled from 'styled-components'; 4 | import _ from 'lodash'; 5 | import { Button, Icon } from 'antd'; 6 | import { Title, Close, View, ListView, Cell, Loading, Check } from '../components'; 7 | import { PostMessage } from '../utils/PostMessage'; 8 | import { join } from 'path'; 9 | /// ///////////////////////////////////////////// 10 | // styled 11 | /// ///////////////////////////////////////////// 12 | 13 | const Cover = styled.div` 14 | width: 2rem; 15 | min-width: 2rem; 16 | height: 2rem; 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | margin-right: 0.5rem; 21 | background: rgba(100, 100, 100, 0.2); 22 | border-radius: 0.1rem; 23 | > img { 24 | max-width: 100%; 25 | max-height: 100%; 26 | zoom: 0.5; 27 | } 28 | `; 29 | 30 | const ImgTitle = styled.div` 31 | transition: all 0.2s ease-out; 32 | margin-top: 1rem; 33 | width: 100%; 34 | font-size: 0.7rem; 35 | text-align: center; 36 | color: #666; 37 | `; 38 | 39 | const Img = styled.div` 40 | display: block; 41 | margin-bottom: 3rem; 42 | cursor: pointer; 43 | width: 100%; 44 | display: flex; 45 | flex-direction: column; 46 | align-items: center; 47 | img { 48 | transition: all 0.2s ease-out; 49 | zoom: 0.5; 50 | max-width: 100%; 51 | max-height: 6rem; 52 | } 53 | &:hover { 54 | ${ImgTitle} { 55 | color: #2b79ff; 56 | } 57 | img { 58 | transform: scale(1.05); 59 | } 60 | } 61 | &:active { 62 | img { 63 | transform: scale(0.95); 64 | } 65 | } 66 | `; 67 | 68 | const LibraryView = styled.div` 69 | height: 100%; 70 | flex: 1; 71 | padding: 1rem; 72 | background: #eee; 73 | overflow-y: auto; 74 | `; 75 | 76 | const ListBtn = styled.div` 77 | flex: 1; 78 | display: flex; 79 | align-items: center; 80 | justify-content: center; 81 | font-size: 0.8rem; 82 | background: rgba(100, 100, 100, 0.3); 83 | padding: 0.2rem; 84 | margin-bottom: 0.8rem; 85 | border-radius: 0.2rem; 86 | 87 | cursor: pointer; 88 | &:hover { 89 | background: rgba(100, 100, 100, 0.5); 90 | } 91 | `; 92 | 93 | /// ///////////////////////////////////////////// 94 | // connect 95 | /// ///////////////////////////////////////////// 96 | const State = state => { 97 | return { 98 | check: state.check, 99 | loading: state.loading.global || Object.keys(state.symbols).length === 0, 100 | symbols: state.symbols, 101 | ...state.store, 102 | }; 103 | }; 104 | 105 | const Dispatch = dispatch => ({ 106 | getSymbols() { 107 | dispatch({ type: `symbols/get` }); 108 | }, 109 | }); 110 | 111 | /// ///////////////////////////////////////////// 112 | // component 113 | /// ///////////////////////////////////////////// 114 | 115 | class Symbols extends Component { 116 | state = { 117 | local: false, 118 | tab: '场景', 119 | activeLocal: false, 120 | activeHeader: {}, 121 | }; 122 | 123 | localStorageName = 'dropdown-symbols'; 124 | 125 | componentDidMount() { 126 | this.props.getSymbols(); 127 | if (this.props.mode === '交互') this.setState({ tab: '交互' }); 128 | } 129 | 130 | SwitchTitle = ({ name }) => ( 131 | this.setState({ tab: name })}> 132 | {name} 133 | 134 | ); 135 | 136 | Cell = ({ name, data, type }) => { 137 | const List = []; 138 | _.forEach(data, (value, title) => 139 | List.push( 140 | this.handleGroup(type, name, title)}> 141 | 142 | 143 | 144 | {title} 145 | 146 | ) 147 | ); 148 | return List; 149 | }; 150 | 151 | Cells = ({ data, type }) => { 152 | const List = []; 153 | if (!this.state.activeHeader[type]) { 154 | const header = this.state.activeHeader; 155 | header[type] = {}; 156 | this.setState({ activeHeader: header }); 157 | } 158 | _.forEach(data, (value, key) => { 159 | const active = !this.state.activeHeader[type][key]; 160 | List.push( 161 |
162 | this.handleHeader(type, key)}> 163 | {key} 164 | 165 | 166 | 167 | 168 |
169 | ); 170 | }); 171 | return ( 172 | 173 | {this.props.check ? ( 174 | this.handleRule(this.state.tab)}> 175 | 176 | 查看规范 177 | 178 | ) : null} 179 | {List} 180 | 181 | ); 182 | }; 183 | 184 | Library = ({ data, type }) => { 185 | const List = []; 186 | let activeRoot; 187 | let activeGroup; 188 | if (!this.state[type]) { 189 | activeRoot = _.keys(data)[0]; 190 | activeGroup = _.keys(data[activeRoot])[0]; 191 | this.setState({ [type]: { activeRoot, activeGroup } }); 192 | } else { 193 | activeRoot = this.state[type].activeRoot; 194 | activeGroup = this.state[type].activeGroup; 195 | } 196 | const SymbolData = data[activeRoot][activeGroup]; 197 | _.forEach(SymbolData, (value, key) => { 198 | const postData = { 199 | id: value.id, 200 | libname: this.props.symbols[type].libname, 201 | type, 202 | }; 203 | List.push( 204 | PostMessage('handleSymbol', JSON.stringify(postData))}> 205 | 206 | {this.getName(value.name[2])} 207 | 208 | ); 209 | }); 210 | return {List}; 211 | }; 212 | 213 | View = ({ data, type }) => ( 214 | 215 | 216 | 217 | 218 | ); 219 | 220 | MainView = () => { 221 | const List = []; 222 | _.forEach(this.props.symbols, t => { 223 | List.push(); 224 | }); 225 | return List; 226 | }; 227 | 228 | ChildView = ({ name, type }) => { 229 | return this.state.tab === name ? ( 230 | 231 | ) : null; 232 | }; 233 | 234 | CheckView = () => (this.props.loading ? : ); 235 | 236 | Tabs = () => { 237 | let Tab = []; 238 | _.forEach(this.props.symbols, t => { 239 | Tab.push(); 240 | }); 241 | return Tab; 242 | }; 243 | 244 | render() { 245 | return ( 246 | <> 247 | {this.props.loading ? null : <this.Tabs />} 248 | {this.props.check ? : } 249 | 250 | 251 | ); 252 | } 253 | 254 | getImg = (type, id) => { 255 | return 'http://' + join('anto.inc.alipay.net/static', type, 'img', id + '.png'); 256 | }; 257 | 258 | getName = name => { 259 | let newName = name.split('-'); 260 | return newName.length === 2 ? newName[1] : name; 261 | }; 262 | 263 | handleRule = tab => { 264 | PostMessage('handleRule', tab); 265 | }; 266 | 267 | handleHeader = (type, name) => { 268 | const newState = this.state.activeHeader; 269 | newState[type][name] = !this.state.activeHeader[type][name]; 270 | this.setState({ activeHeader: newState }); 271 | localStorage.setItem(this.localStorageName, JSON.stringify(newState)); 272 | }; 273 | 274 | handleGroup = (type, activeRoot, activeGroup) => { 275 | this.setState({ [type]: { activeRoot, activeGroup } }); 276 | }; 277 | } 278 | 279 | export default connect( 280 | State, 281 | Dispatch 282 | )(Symbols); 283 | -------------------------------------------------------------------------------- /panel/routes/index.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { connect } from 'dva'; 3 | import styled from 'styled-components'; 4 | import { Iconfont } from '../components'; 5 | import QueueAnim from 'rc-queue-anim'; 6 | import { PostMessage } from '../utils/PostMessage'; 7 | // Panel 8 | import Symbols from './Symbols'; 9 | import Colors from './Colors'; 10 | import Line from './Line'; 11 | import Note from './Note'; 12 | import Layer from './Layer'; 13 | import Plate from './Plate'; 14 | import Docs from './Docs'; 15 | import Dev from './Dev'; 16 | import Setting from './Setting'; 17 | 18 | /// ///////////////////////////////////////////// 19 | // styled 20 | /// ///////////////////////////////////////////// 21 | 22 | const View = styled.div` 23 | display: flex; 24 | color: ${props => (props.theme === 'black' ? '#aaa' : '#777')}; 25 | background: ${props => (props.theme === 'black' ? '#222' : '#f5f5f5')}; 26 | `; 27 | 28 | const SideBar = styled.div` 29 | background: ${props => (props.theme === 'black' ? '#222' : '#f5f5f5')}; 30 | width: 48px; 31 | height: 100vh; 32 | overflow: hidden; 33 | position: fixed; 34 | display: flex; 35 | flex-direction: column; 36 | align-items: center; 37 | top: 0; 38 | left: 0; 39 | z-index: 100; 40 | border-right: ${props => 41 | props.theme === 'black' ? '1px solid rgba(0, 0, 0, 0.1)' : '1px solid #eee'}; 42 | z-index: 999; 43 | `; 44 | 45 | const Panel = styled.div` 46 | margin-left: 48px; 47 | width: 100%; 48 | flex: 1; 49 | height: 100vh; 50 | `; 51 | 52 | const Logo = styled.div` 53 | width: 100%; 54 | height: 48px; 55 | background-image: url('logo.png'); 56 | background-position: center; 57 | background-repeat: no-repeat; 58 | background-size: 28px auto; 59 | box-shadow: ${props => 60 | props.theme === 'black' ? '0 4px 12px rgba(0, 0, 0, 0.2)' : '0 4px 8px rgba(0, 0, 0, 0.05)'}; 61 | `; 62 | 63 | const Mode = styled.div` 64 | position: fixed; 65 | bottom: 1.8rem; 66 | width: 2.5rem; 67 | color: #fff; 68 | font-size: 0.7rem; 69 | text-align: center; 70 | line-height: 1.5rem; 71 | opacity: 0.4; 72 | transition: all 0.2s ease-out; 73 | cursor: pointer; 74 | border-radius: 1.5rem; 75 | &:hover { 76 | opacity: 1; 77 | } 78 | &:active { 79 | transform: scale(0.9); 80 | } 81 | `; 82 | 83 | const Close = styled.div` 84 | position: fixed; 85 | bottom: 0; 86 | width: 3rem; 87 | height: 2rem; 88 | line-height: 2rem; 89 | text-align: center; 90 | opacity: 0.2; 91 | transition: all 0.2s ease-out; 92 | cursor: pointer; 93 | &:hover { 94 | opacity: 1; 95 | } 96 | &:active { 97 | transform: scale(0.9); 98 | } 99 | `; 100 | 101 | /// ///////////////////////////////////////////// 102 | // connect 103 | /// ///////////////////////////////////////////// 104 | 105 | const State = state => { 106 | return { 107 | ...state.store, 108 | ...state.config, 109 | }; 110 | }; 111 | 112 | const Dispatch = dispatch => ({ 113 | setConfig(obj) { 114 | dispatch({ type: `config/update`, payload: obj }); 115 | }, 116 | setStore(obj) { 117 | dispatch({ type: `store/update`, payload: obj }); 118 | }, 119 | check() { 120 | dispatch({ type: `check/get` }); 121 | }, 122 | }); 123 | 124 | /// ///////////////////////////////////////////// 125 | // component 126 | /// ///////////////////////////////////////////// 127 | 128 | class WebView extends Component { 129 | state = { 130 | symbol: 370, 131 | color: 220, 132 | setting: 250, 133 | word: 370, 134 | }; 135 | 136 | componentDidMount() { 137 | this.props.check(); 138 | } 139 | 140 | SideBar = () => ( 141 | 142 | 143 | 144 | this.openPanel('symbol', this.state.symbol)} 149 | more 150 | /> 151 | this.openPanel('color', this.state.color)} 156 | more 157 | /> 158 | this.openPanel('line')} 163 | more 164 | /> 165 | this.openPanel('layer')} 170 | more 171 | /> 172 | this.openPanel('note')} 177 | more 178 | /> 179 | this.openPanel('plate')} 184 | more 185 | /> 186 | this.openPanel('word', this.state.word)} 191 | more 192 | /> 193 | PostMessage('handleYuque', null)} 198 | /> 199 | {this.props.devMode ? ( 200 | this.openPanel('dev')} 205 | /> 206 | ) : null} 207 | this.openPanel('setting', this.state.setting)} 212 | /> 213 | 214 | 219 | {this.props.mode} 220 | 221 | PostMessage('handleClose', null)} /> 222 | 223 | ); 224 | 225 | render() { 226 | return ( 227 | 228 | 229 | 230 | {this.props.symbol ? : null} 231 | {this.props.color ? : null} 232 | {this.props.line ? : null} 233 | {this.props.note ? : null} 234 | {this.props.layer ? : null} 235 | {this.props.plate ? : null} 236 | {this.props.word ? : null} 237 | {this.props.dev ? : null} 238 | {this.props.setting ? : null} 239 | 240 | 241 | ); 242 | } 243 | 244 | handleChangeMode = () => { 245 | const preMode = this.props.mode; 246 | const mode = preMode === '视觉' ? '交互' : '视觉'; 247 | this.props.setStore({ mode }); 248 | PostMessage('changeMode', mode); 249 | }; 250 | 251 | openPanel = (name, width = null) => { 252 | PostMessage('openPanel', width); 253 | this.props.setConfig({ [name]: true }); 254 | }; 255 | } 256 | 257 | export default connect( 258 | State, 259 | Dispatch 260 | )(WebView); 261 | -------------------------------------------------------------------------------- /panel/utils/PostMessage.js: -------------------------------------------------------------------------------- 1 | export const PostMessage = (name, data) => { 2 | try { 3 | window.postMessage(name, data); 4 | } catch (e) { 5 | console.log(name, data); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /panel/utils/request.js: -------------------------------------------------------------------------------- 1 | import fetch from 'dva/fetch'; 2 | 3 | const parseJSON = response => { 4 | return response.json(); 5 | }; 6 | 7 | const checkStatus = response => { 8 | if (response.status >= 200 && response.status < 300) return response; 9 | const error = new Error(response.statusText); 10 | error.response = response; 11 | throw error; 12 | }; 13 | 14 | export const request = (url, options) => { 15 | return fetch(url, options) 16 | .then(checkStatus) 17 | .then(parseJSON) 18 | .then(data => ({ data })) 19 | .catch(err => ({ err })); 20 | }; 21 | -------------------------------------------------------------------------------- /preview/.webpackrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | entry : "./src/index.js", 3 | disableCSSModules : true, 4 | ignoreMomentLocale : true, 5 | theme : { 6 | "@primary-color": "#2A72FF", 7 | "@text-color": "#999", 8 | "@heading-color": "#999", 9 | "@border-color-base":"rgba(100,100,100,.3)", 10 | "@input-bg":"rgba(255,255,255,.03)", 11 | "@input-placeholder-color":"rgba(100,100,100,.5)" 12 | }, 13 | html : { 14 | "template": "./src/index.ejs" 15 | }, 16 | define : { 17 | "$dirname": __dirname, 18 | "$isDev" : process.env.NODE_ENV === "development" 19 | }, 20 | extraBabelPlugins : [ 21 | 'lodash', 22 | ['import', {libraryName: 'antd', libraryDirectory: 'es', style: true}] 23 | ], 24 | env : { 25 | development: { 26 | extraBabelPlugins: [ 27 | "dva-hmr", 28 | ["babel-plugin-styled-components", { displayName: true }] 29 | ] 30 | }, 31 | production : { 32 | browserslist : ['iOS >= 8', 'Android >= 4'], 33 | } 34 | } 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /preview/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anto-preview", 3 | "scripts": { 4 | "start": "cross-env ESLINT=none roadhog dev", 5 | "build": "cross-env ESLINT=none roadhog build" 6 | }, 7 | "dependencies": { 8 | "antd": "^3.10.9", 9 | "dva": "^2.4.1", 10 | "dva-loading": "^2.0.6", 11 | "lodash": "^4.17.11", 12 | "react": "^16.6.3", 13 | "react-dom": "^16.6.3" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.1.2", 17 | "@babel/preset-env": "^7.1.0", 18 | "@babel/preset-stage-0": "^7.0.0", 19 | "@babel/runtime": "^7.1.2", 20 | "babel-plugin-dva-hmr": "^0.4.1", 21 | "babel-plugin-import": "^1.10.0", 22 | "babel-plugin-lodash": "^3.3.4", 23 | "babel-plugin-transform-decorators-legacy": "^1.3.5", 24 | "cross-env": "^5.2.0", 25 | "moment": "^2.22.2", 26 | "path": "^0.12.7", 27 | "polished": "^2.3.1", 28 | "rc-queue-anim": "^1.6.8", 29 | "redbox-react": "^1.3.2", 30 | "roadhog": "2.5.0-beta.1", 31 | "styled-components": "^4.0.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /preview/public/data.js: -------------------------------------------------------------------------------- 1 | localStorage.setItem( 2 | 'preview', 3 | '{"date":"2018-12-31","author":"花名","pages":[{"path":"preview/Page 1 (视觉) 1231.png","name":"Page 1","mode":"视觉","date":"1231","width":1175,"height":1892},{"path":"preview/a (视觉) 1231.png","name":"a","mode":"视觉","date":"1231","width":1728,"height":1976},{"path":"preview/Page 2 (交互) 1231.png","name":"Page 2","mode":"交互","date":"1231","width":1214,"height":1976}]}' 4 | ); 5 | -------------------------------------------------------------------------------- /preview/public/preview/Page 1 (视觉) 1231.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/preview/public/preview/Page 1 (视觉) 1231.png -------------------------------------------------------------------------------- /preview/public/preview/Page 2 (交互) 1231.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/preview/public/preview/Page 2 (交互) 1231.png -------------------------------------------------------------------------------- /preview/public/preview/a (视觉) 1231.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/preview/public/preview/a (视觉) 1231.png -------------------------------------------------------------------------------- /preview/src/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #root { 4 | height: 100%; 5 | overflow: hidden; 6 | user-select: none; 7 | background: #222; 8 | } 9 | 10 | body { 11 | -webkit-font-smoothing: antialiased; 12 | -moz-osx-font-smoothing: grayscale; 13 | -ms-text-size-adjust: 100%; 14 | -webkit-text-size-adjust: 100%; 15 | } 16 | 17 | *:focus { 18 | outline: none; 19 | } 20 | 21 | * { 22 | -webkit-font-smoothing: antialiased; 23 | } 24 | 25 | div { 26 | box-sizing: border-box; 27 | position: relative; 28 | } 29 | 30 | .logo { 31 | width: 120px; 32 | height: 50px; 33 | background-size: contain; 34 | background-image:url('data:image/png;base64,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'); 35 | } -------------------------------------------------------------------------------- /preview/src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | Anto 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /preview/src/index.js: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | import dva from 'dva'; 3 | import createLoading from 'dva-loading'; 4 | import './index.css'; 5 | 6 | // 1. Initialize 7 | const app = dva({ 8 | onError(e) { 9 | message.error(e.message, 3); 10 | }, 11 | }); 12 | 13 | // 2. Models 14 | app.model(require('./models/preview').default); 15 | 16 | // 2. Plugins 17 | app.use(createLoading()); 18 | 19 | // 3. Router 20 | app.router(require('./router').default); 21 | 22 | // 4. Start 23 | app.start('#root'); 24 | -------------------------------------------------------------------------------- /preview/src/models/preview.js: -------------------------------------------------------------------------------- 1 | export default { 2 | namespace: 'preview', 3 | state: {}, 4 | reducers: { 5 | save(state, action) { 6 | state = action.payload; 7 | return state; 8 | }, 9 | }, 10 | effects: { 11 | *get(action, { call, put }) { 12 | yield put({ 13 | type: 'save', 14 | payload: JSON.parse(localStorage.getItem('preview')), 15 | }); 16 | }, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /preview/src/router.js: -------------------------------------------------------------------------------- 1 | import { Route, Router } from 'dva/router'; 2 | import App from './routes'; 3 | 4 | export default ({ app, history }) => { 5 | history.listen(() => window.scrollTo(0, 0)); 6 | return ( 7 | 8 | 9 | 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /preview/src/routes/index.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { connect } from 'dva'; 3 | import styled from 'styled-components'; 4 | import { Icon } from 'antd'; 5 | 6 | /// ///////////////////////////////////////////// 7 | // styled 8 | /// ///////////////////////////////////////////// 9 | 10 | const View = styled.div` 11 | width: 100%; 12 | height: 100%; 13 | `; 14 | 15 | const Privew = styled.div` 16 | display: flex; 17 | `; 18 | 19 | const Showcase = styled.div` 20 | width: 100%; 21 | height: 100vh; 22 | `; 23 | 24 | const Case = styled.div` 25 | overflow: auto; 26 | width: 100%; 27 | height: calc(100% - 4rem); 28 | `; 29 | 30 | const ImgCase = styled.div` 31 | > img { 32 | position: absolute; 33 | top: 0; 34 | left: 0; 35 | } 36 | `; 37 | 38 | const Header = styled.div` 39 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); 40 | height: 4rem; 41 | padding: 0 0.5rem; 42 | display: flex; 43 | align-items: center; 44 | justify-content: space-between; 45 | z-index: 99; 46 | `; 47 | 48 | const List = styled.div` 49 | border-right: 1px solid rgba(255, 255, 255, 0.03); 50 | height: 100vh; 51 | overflow-x: hidden; 52 | overflow-y: auto; 53 | `; 54 | 55 | const Cell = styled.div` 56 | width: 20rem; 57 | padding: 1rem; 58 | display: flex; 59 | align-items: center; 60 | border-bottom: 1px solid rgba(255, 255, 255, 0.03); 61 | cursor: pointer; 62 | background: ${props => (props.active ? 'rgba(255,255,255,.02)' : '#222')}; 63 | &:hover { 64 | background: #1f1f1f; 65 | } 66 | `; 67 | 68 | const Cover = styled.div` 69 | width: 3rem; 70 | height: 3rem; 71 | min-width: 3rem; 72 | background: url("${props => props.path}"); 73 | background-size: cover; 74 | background-repeat: no-repeat; 75 | border-radius: .2rem; 76 | box-shadow: 0 4px 8px rgba(0,0,0,.2); 77 | `; 78 | 79 | const Content = styled.div` 80 | margin-left: 1rem; 81 | `; 82 | const Title = styled.div` 83 | display: flex; 84 | font-size: 0.9rem; 85 | align-items: center; 86 | overflow: hidden; 87 | text-overflow: ellipsis; 88 | white-space: nowrap; 89 | -webkit-line-clamp: 1; 90 | width: 14rem; 91 | font-weight: 500; 92 | `; 93 | 94 | const Tag = styled.div` 95 | font-size: 0.7rem; 96 | padding: 0.1rem 0.3rem; 97 | border-radius: 0.2rem; 98 | background: ${props => (props.mode === '交互' ? '#2A72FF' : '#333')}; 99 | margin-right: 0.5rem; 100 | color: #fff; 101 | `; 102 | 103 | const Date = styled.div` 104 | color: #555; 105 | font-size: 0.8rem; 106 | margin-top: 0.2rem; 107 | `; 108 | 109 | const Group = styled.div` 110 | display: flex; 111 | align-items: center; 112 | `; 113 | 114 | const Zoom = styled.div` 115 | display: flex; 116 | align-items: center; 117 | margin: 0 3rem 0 1rem; 118 | `; 119 | 120 | const ZoomNum = styled.div` 121 | background: #181818; 122 | height: 2rem; 123 | display: flex; 124 | align-items: center; 125 | justify-content: center; 126 | width: 4rem; 127 | border-radius: 1rem; 128 | margin: 0 0.5rem; 129 | cursor: pointer; 130 | &:active { 131 | background: #111; 132 | } 133 | `; 134 | 135 | const ZoomIcon = styled.div` 136 | background: #2a72ff; 137 | height: 1.5rem; 138 | width: 1.5rem; 139 | color: #fff; 140 | display: flex; 141 | align-items: center; 142 | justify-content: center; 143 | border-radius: 50%; 144 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); 145 | cursor: pointer; 146 | &:active { 147 | background: #155cd5; 148 | } 149 | `; 150 | 151 | const PageTitle = styled.div` 152 | font-size: 1.2rem; 153 | color: #fff; 154 | font-weight: 600; 155 | `; 156 | 157 | const Author = styled.div` 158 | background: #333; 159 | padding: 0.5rem 0.8rem; 160 | border: 2px solid #222; 161 | outline: 1px solid #333; 162 | `; 163 | 164 | const Name = styled.span` 165 | color: #fff; 166 | font-weight: 600; 167 | margin-right: 0.5rem; 168 | `; 169 | 170 | const Time = styled.span` 171 | font-size: 0.8rem; 172 | `; 173 | 174 | /// ///////////////////////////////////////////// 175 | // connect 176 | /// ///////////////////////////////////////////// 177 | 178 | const State = state => { 179 | return { 180 | loading: state.loading.models.preview || !state.preview.date, 181 | preview: state.preview, 182 | }; 183 | }; 184 | 185 | const Dispatch = dispatch => ({ 186 | getPreview() { 187 | dispatch({ type: 'preview/get' }); 188 | }, 189 | }); 190 | 191 | /// ///////////////////////////////////////////// 192 | // component 193 | /// ///////////////////////////////////////////// 194 | 195 | class App extends Component { 196 | state = { 197 | zoom: 1, 198 | }; 199 | 200 | componentDidMount() { 201 | this.props.getPreview(); 202 | } 203 | 204 | List = page => { 205 | return ( 206 | this.handleClick(page)} 210 | > 211 | 212 | 213 | {page.name} 214 | 215 | {page.date[0]} 216 | {page.date[1]}-{page.date[2]} 217 | {page.date[3]} | {page.mode} 218 | 219 | 220 | 221 | ); 222 | }; 223 | 224 | Page = ({ data }) => { 225 | if (!this.state.name) this.setState({ ...data.pages[0] }); 226 | return ( 227 | 228 | 229 |
230 | 236 |
237 | {data.pages.map(this.List)} 238 |
239 | 240 |
241 | 242 | 243 | 244 | 245 | 246 | {Math.floor(this.state.zoom * 100)}% 247 | 248 | 249 | 250 | 251 | 252 | {this.state.name} ({this.state.mode}) 253 | 254 | 255 | 256 | {data.author} 257 | 258 | 259 |
260 | 261 | 262 | 270 | 271 | 272 |
273 |
274 | ); 275 | }; 276 | 277 | render() { 278 | return ( 279 | {this.props.loading ? 'Loading...' : } 280 | ); 281 | } 282 | 283 | handleReset = () => { 284 | this.setState({ zoom: 1 }); 285 | }; 286 | 287 | handleMinus = () => { 288 | const zoom = this.state.zoom * 0.8; 289 | this.setState({ zoom }); 290 | }; 291 | 292 | handlePlus = () => { 293 | const zoom = this.state.zoom * 1.2; 294 | this.setState({ zoom }); 295 | }; 296 | 297 | handleClick = page => { 298 | this.setState({ ...page, zoom: 1 }); 299 | }; 300 | } 301 | 302 | export default connect( 303 | State, 304 | Dispatch 305 | )(App); 306 | -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/public/icon.png -------------------------------------------------------------------------------- /public/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1554361695475'); /* IE9 */ 3 | src: url('iconfont.eot?t=1554361695475#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), 5 | url('iconfont.woff?t=1554361695475') format('woff'), 6 | url('iconfont.ttf?t=1554361695475') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1554361695475#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-main-layer:before { 19 | content: "\e601"; 20 | } 21 | 22 | .icon-main-plate:before { 23 | content: "\e602"; 24 | } 25 | 26 | .icon-main-note:before { 27 | content: "\e603"; 28 | } 29 | 30 | .icon-main-line:before { 31 | content: "\e604"; 32 | } 33 | 34 | .icon-main-color:before { 35 | content: "\e605"; 36 | } 37 | 38 | .icon-main-symbol:before { 39 | content: "\e606"; 40 | } 41 | 42 | .icon-main-yuque:before { 43 | content: "\e607"; 44 | } 45 | 46 | .icon-line-note:before { 47 | content: "\e608"; 48 | } 49 | 50 | .icon-line-dash:before { 51 | content: "\e609"; 52 | } 53 | 54 | .icon-line-change:before { 55 | content: "\e60a"; 56 | } 57 | 58 | .icon-line-if:before { 59 | content: "\e60b"; 60 | } 61 | 62 | .icon-line-link:before { 63 | content: "\e60c"; 64 | } 65 | 66 | .icon-layer-bottom-lite:before { 67 | content: "\e60e"; 68 | } 69 | 70 | .icon-layer-sort:before { 71 | content: "\e60f"; 72 | } 73 | 74 | .icon-layer-top-lite:before { 75 | content: "\e611"; 76 | } 77 | 78 | .icon-layer-layout:before { 79 | content: "\e610"; 80 | } 81 | 82 | .icon-layer-height:before { 83 | content: "\e613"; 84 | } 85 | 86 | .icon-note-list:before { 87 | content: "\e614"; 88 | } 89 | 90 | .icon-note-point:before { 91 | content: "\e615"; 92 | } 93 | 94 | .icon-note-changelog:before { 95 | content: "\e617"; 96 | } 97 | 98 | .icon-note-text:before { 99 | content: "\e619"; 100 | } 101 | 102 | .icon-note-title:before { 103 | content: "\e61a"; 104 | } 105 | 106 | .icon-note-block:before { 107 | content: "\e61f"; 108 | } 109 | 110 | .icon-plate-ignore:before { 111 | content: "\e616"; 112 | } 113 | 114 | .icon-plate-artboard:before { 115 | content: "\e618"; 116 | } 117 | 118 | .icon-plate-export:before { 119 | content: "\e61b"; 120 | } 121 | 122 | .icon-plate-title:before { 123 | content: "\e61c"; 124 | } 125 | 126 | .icon-note-quoter:before { 127 | content: "\e61d"; 128 | } 129 | 130 | .icon-note-subtitle:before { 131 | content: "\e61e"; 132 | } 133 | 134 | .icon-layer-top:before { 135 | content: "\e612"; 136 | } 137 | 138 | .icon-layer-bottom:before { 139 | content: "\e620"; 140 | } 141 | 142 | .icon-close:before { 143 | content: "\e621"; 144 | } 145 | 146 | .icon-setting:before { 147 | content: "\e622"; 148 | } 149 | 150 | .icon-plate-number:before { 151 | content: "\e60d"; 152 | } 153 | 154 | .icon-more:before { 155 | content: "\e623"; 156 | } 157 | 158 | .icon-main-word:before { 159 | content: "\e624"; 160 | } 161 | 162 | .icon-layer-blender:before { 163 | content: "\e625"; 164 | } 165 | 166 | .icon-layer-looper:before { 167 | content: "\e626"; 168 | } 169 | 170 | .icon-main-config:before { 171 | content: "\e627"; 172 | } 173 | 174 | -------------------------------------------------------------------------------- /public/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/public/iconfont/iconfont.eot -------------------------------------------------------------------------------- /public/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/public/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /public/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/public/iconfont/iconfont.woff -------------------------------------------------------------------------------- /public/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/public/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/public/logo.png -------------------------------------------------------------------------------- /public/minicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/public/minicon.png -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | webhook.json -------------------------------------------------------------------------------- /scripts/ding.js: -------------------------------------------------------------------------------- 1 | const fetch = require("node-fetch"); 2 | const fs = require("fs"); 3 | const _ = require("lodash"); 4 | const webhook = require("./webhook.json"); 5 | 6 | fetch("https://api.github.com/repos/canisminor1990/anto/releases/latest") 7 | .then(res => res.json()) 8 | .then(json => { 9 | const { name, body, assets } = json; 10 | const title = `${name} 现已发布 🚀`; 11 | const cover = "https://raw.githubusercontent.com/canisminor1990/anto/master/docs/banner.png"; 12 | const feedback = "- 意见反馈:@倏昱 (钉钉群: 21941480)"; 13 | const postData = { 14 | actionCard: { 15 | title : title, 16 | text : `![screenshot](${cover}) \n\n ## ${title} \n\n ${body} \n\n ${feedback}`, 17 | btnOrientation: "0", 18 | btns : [ 19 | { 20 | title : "查看更新说明", 21 | actionURL: "https://www.yuque.com/canisminor/anto/changelog" 22 | }, 23 | { 24 | title : "下载插件", 25 | actionURL: assets[0].browser_download_url 26 | } 27 | ] 28 | }, 29 | msgtype : "actionCard" 30 | }; 31 | 32 | _.forEach(webhook, url => fetchPost(url, JSON.stringify(postData))); 33 | }); 34 | 35 | function fetchPost(url, body) { 36 | const config = { 37 | method : "POST", 38 | headers: { 39 | "Content-Type": "application/json" 40 | }, 41 | body : body 42 | }; 43 | fetch(url, config) 44 | .then((response) => { 45 | console.log(url); 46 | }); 47 | } 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /scripts/preview.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | const rootDir = "preview/dist"; 5 | const css = fs.readFileSync(path.join(rootDir, "index.css"), "utf-8"); 6 | const html = fs.readFileSync(path.join(rootDir, "index.html"), "utf-8"); 7 | const js = fs.readFileSync(path.join(rootDir, "index.js"), "utf-8"); 8 | 9 | const Data = { css, html, js }; 10 | fs.writeFileSync("src/preview.json", JSON.stringify(Data)); 11 | 12 | console.log(JSON.stringify(Data)) -------------------------------------------------------------------------------- /src/api/create.js: -------------------------------------------------------------------------------- 1 | import sketch from 'sketch/dom'; 2 | import _ from 'lodash'; 3 | export default class SketchCreate { 4 | group(config) { 5 | return new sketch.Group( 6 | _.defaults(config, { 7 | frame: { 8 | x: -50000, 9 | y: -50000, 10 | width: 100000, 11 | height: 100000, 12 | }, 13 | layers: [], 14 | }) 15 | ); 16 | } 17 | 18 | page(config) { 19 | return new sketch.Page(config); 20 | } 21 | 22 | artboard(config) { 23 | return new sketch.Artboard(config); 24 | } 25 | 26 | shape(config) { 27 | return new sketch.Shape(config); 28 | } 29 | 30 | image(config) { 31 | return new sketch.Image(config); 32 | } 33 | 34 | text(config) { 35 | return new sketch.Text(config); 36 | } 37 | 38 | symbolMaster(artboard) { 39 | return new sketch.SymbolMaster.fromArtboard(artboard); 40 | } 41 | 42 | instance(master) { 43 | return master.createNewInstance(); 44 | } 45 | 46 | slice(config) { 47 | return new sketch.Slice(config); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/api/layer.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export default class SketchLayer { 4 | get(layer, name) { 5 | return _.filter(layer.layers, l => l.name === name); 6 | } 7 | 8 | getById(document, id) { 9 | return document.getLayerWithID(id); 10 | } 11 | 12 | deepGet(layer, name) { 13 | let layers = []; 14 | _.forEach(layer.layers, l => { 15 | if (l.name === name) layers.push(l); 16 | if (l.layers) layers.concat(this.deepGet(l.layers, name)); 17 | }); 18 | return layers; 19 | } 20 | 21 | globalGet(document, name) { 22 | try { 23 | return document.getLayersNamed(name); 24 | } catch (e) { 25 | return []; 26 | } 27 | } 28 | 29 | remove(layer, name) { 30 | _.forEach(layer.layers, l => { 31 | if (l.name === name) l.remove(); 32 | }); 33 | } 34 | 35 | deepRemove(layer, name) { 36 | _.forEach(layer.layers, l => { 37 | if (l.name === name) l.remove(); 38 | if (l.layers) this.deepRemove(l.layers, name); 39 | }); 40 | } 41 | 42 | globalRemove(document, name) { 43 | const layers = this.globalGet(document, name); 44 | if (layers.length > 0) _.forEach(layers, l => l.remove()); 45 | } 46 | 47 | getArtboard(layer) { 48 | if (layer.parent.type === 'Artboard') return layer.parent; 49 | if (layer.parent.type === 'Page') return false; 50 | return this.getArtboard(layer.parent); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/api/library.js: -------------------------------------------------------------------------------- 1 | import sketch from 'sketch/dom'; 2 | import UI from 'sketch/ui'; 3 | import _ from 'lodash'; 4 | 5 | export default class SketchLibrary { 6 | get all() { 7 | return sketch.Library.getLibraries(); 8 | } 9 | 10 | get antoExport() { 11 | return this.get('Anto Export'); 12 | } 13 | 14 | get ux() { 15 | return this.get('Anto UX'); 16 | } 17 | 18 | get ui() { 19 | return this.get('Anto UI'); 20 | } 21 | 22 | get(name) { 23 | const result = _.filter(this.all, lib => lib.name === name); 24 | if (result.length > 0) return result[0]; 25 | UI.message(`😥 请检查「${name}」是否存在`); 26 | return false; 27 | } 28 | getSymbols(library) { 29 | return library.getImportableSymbolReferencesForDocument(sketch.getSelectedDocument()); 30 | } 31 | getSymbol(library, name) { 32 | const librarySymbols = this.getSymbols(library); 33 | const result = _.filter(librarySymbols, s => s.name === name); 34 | return result.length > 0 ? result[0] : false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/api/native.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import moment from 'moment'; 3 | import { join } from 'path'; 4 | 5 | export default class SketchNative { 6 | get context() { 7 | return NSDocumentController.sharedDocumentController(); 8 | } 9 | 10 | get document() { 11 | return this.context.currentDocument(); 12 | } 13 | 14 | get pages() { 15 | return this.document.pages(); 16 | } 17 | 18 | get page() { 19 | return this.document.currentPage(); 20 | } 21 | 22 | get selection() { 23 | return this.document.selectedLayers(); 24 | } 25 | 26 | get firstSelectLayer() { 27 | return this.selection.layers()[0]; 28 | } 29 | 30 | setName(layer, name) { 31 | layer.setName(name); 32 | } 33 | 34 | setLocked(layer, bool = true) { 35 | layer.setIsLocked(bool); 36 | } 37 | 38 | setVisible(layer, bool = true) { 39 | layer.setIsVisible(bool); 40 | } 41 | 42 | setX(layer, x) { 43 | layer.frame().setX(x); 44 | } 45 | 46 | setY(layer, y) { 47 | layer.frame().setY(y); 48 | } 49 | 50 | setWidth(layer, width) { 51 | layer.frame().setWidth(width); 52 | } 53 | 54 | setHeight(layer, height) { 55 | layer.frame().setHeight(height); 56 | } 57 | 58 | moveToFront(layer) { 59 | MSLayerMovement.moveToFront(_.isArray(layer) ? layer : [layer]); 60 | } 61 | 62 | remove(layer) { 63 | layer.parentGroup().removeLayer(layer); 64 | } 65 | 66 | addLayers(group, layer) { 67 | group.addLayers(_.isArray(layer) ? layer : [layer]); 68 | } 69 | 70 | setSeleted(layer) { 71 | layer.select_byExtendingSelection(true, true); 72 | } 73 | 74 | setSlice(layer, name, size = 1, type = 'x') { 75 | const sliceLayer = MSSliceLayer.new(); 76 | this.setName(sliceLayer, name); 77 | this.setLocked(sliceLayer); 78 | this.setVisible(sliceLayer, false); 79 | this.setX(sliceLayer, layer.frame.x); 80 | this.setY(sliceLayer, layer.frame.y); 81 | this.setWidth(sliceLayer, layer.frame.width); 82 | this.setHeight(sliceLayer, layer.frame.height); 83 | this.setExport(sliceLayer, size, type); 84 | return sliceLayer; 85 | } 86 | 87 | setExport(layer, size = 1, type = 'x') { 88 | const Slice = layer.exportOptions().addExportFormat(); 89 | Slice.setFileFormat('jpg'); 90 | Slice.setNamingScheme(0); 91 | if (type === 'w') { 92 | Slice.setVisibleScaleType(1); 93 | Slice.setScale(0); 94 | Slice.setAbsoluteSize(parseInt(size, 10)); 95 | } else if (type === 'w') { 96 | Slice.setVisibleScaleType(2); 97 | Slice.setScale(0); 98 | Slice.setAbsoluteSize(parseInt(size, 10)); 99 | } else if (type === 'x') { 100 | Slice.setVisibleScaleType(0); 101 | Slice.setScale(parseInt(size, 10)); 102 | Slice.setAbsoluteSize(0); 103 | } 104 | } 105 | 106 | exportSlice(slice, path, name) { 107 | this.document.saveArtboardOrSlice_toFile(slice, join(path, name + '.jpg')); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/api/setting.js: -------------------------------------------------------------------------------- 1 | import Settings from 'sketch/settings'; 2 | 3 | export default class SketchSetting { 4 | get(key) { 5 | return Settings.settingForKey(key); 6 | } 7 | 8 | set(key, value) { 9 | return Settings.setSettingForKey(key, value); 10 | } 11 | 12 | getLayer(layer, key) { 13 | return Settings.layerSettingForKey(layer, key); 14 | } 15 | 16 | setLayer(layer, key, value) { 17 | return Settings.setLayerSettingForKey(layer, key, value); 18 | } 19 | 20 | getDocument(document, key) { 21 | return Settings.documentSettingForKey(document, key); 22 | } 23 | 24 | setDocument(document, key, value) { 25 | return Settings.setDocumentSettingForKey(document, key, value); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/api/ui.js: -------------------------------------------------------------------------------- 1 | import UI from 'sketch/ui'; 2 | 3 | export default class sketchUI { 4 | message(string) { 5 | console.log(string); 6 | UI.message(string); 7 | } 8 | 9 | success(string) { 10 | this.message(`🔵 ${string}`); 11 | } 12 | warn(string) { 13 | this.message(`😥 ${string}`); 14 | } 15 | error(string) { 16 | this.message(`🥵 ${string}`); 17 | } 18 | alert(title, string) { 19 | UI.alert(title, string); 20 | } 21 | inputPanel(title, defalutValue, func) { 22 | return UI.getInputFromUser(title, defalutValue, func); 23 | } 24 | selectPanel(title, options = []) { 25 | const selection = UI.getSelectionFromUser(title, options); 26 | const ok = selection[2]; 27 | const value = options[selection[1]]; 28 | return ok ? value : false; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import BrowserWindow from 'sketch-module-web-view'; 3 | import _ from 'lodash'; 4 | import Options from './options'; 5 | import Router from './router'; 6 | import Sk from './sketch'; 7 | 8 | const isDev = process.env.NODE_ENV === 'development'; 9 | const Sketch = new Sk(); 10 | const Panel = isDev ? 'http://localhost:8000' : 'index.html'; 11 | 12 | export const onRun = context => { 13 | Sketch.setting.set('url', String(context.plugin.url())); 14 | 15 | const browserWindow = new BrowserWindow(Options); 16 | browserWindow.setSize(Options.width, Options.height); 17 | 18 | // 加载完成后显示 19 | browserWindow.once('ready-to-show', () => { 20 | const mode = Sketch.setting.get('panel-mode'); 21 | if (!mode) Sketch.setting.set('panel-mode', '交互'); 22 | const Positon = Sketch.setting.get('panel-position'); 23 | if (_.isArray(Positon)) browserWindow.setPosition(Positon[0], browserWindow.getPosition()[1]); 24 | browserWindow.webContents.executeJavaScript( 25 | `localStorage.setItem("version","${String(context.plugin.version())}")` 26 | ); 27 | browserWindow.show(); 28 | }); 29 | 30 | // 保存移动位置 31 | browserWindow.on('move', () => { 32 | const Positon = browserWindow.getPosition(); 33 | Sketch.setting.set('panel-position', [Positon[0], Positon[1]]); 34 | }); 35 | 36 | // Webview On 37 | new Router(browserWindow).start(); 38 | 39 | // 开始 40 | browserWindow.loadURL(Panel); 41 | }; 42 | 43 | export default onRun; 44 | -------------------------------------------------------------------------------- /src/library.js: -------------------------------------------------------------------------------- 1 | import sketch from 'sketch/dom'; 2 | import _ from 'lodash'; 3 | 4 | const Library = sketch.Library; 5 | 6 | export const addLibrary = context => { 7 | fetch('http://100.88.232.163/static/tabs.json') 8 | .then(res => res.json()) 9 | .then(json => { 10 | let remoteLibrary = ['anto-export.xml']; 11 | let libName = ['Anto Export']; 12 | _.forEach(json, lib => { 13 | remoteLibrary.push(lib.filename + '.xml'); 14 | libName.push(lib.libname); 15 | }); 16 | _.forEach(remoteLibrary, fileName => { 17 | const url = `http://100.88.232.163/library/${fileName}`; 18 | Library.getRemoteLibraryWithRSS(url, err => { 19 | console.log(err); 20 | }); 21 | }); 22 | }); 23 | }; 24 | 25 | export const cleanLibrary = context => { 26 | fetch('http://100.88.232.163/static/tabs.json') 27 | .then(res => res.json()) 28 | .then(json => { 29 | let remoteLibrary = ['anto-export.xml']; 30 | let libName = ['Anto Export']; 31 | _.forEach(json, lib => { 32 | remoteLibrary.push(lib.filename + '.xml'); 33 | libName.push(lib.libname); 34 | }); 35 | 36 | _.forEach(Library.getLibraries(), lib => { 37 | if (_.includes(libName, lib.name)) lib.remove(); 38 | }); 39 | 40 | _.forEach(remoteLibrary, fileName => { 41 | const url = `http://100.88.232.163/library/${fileName}`; 42 | Library.getRemoteLibraryWithRSS(url, err => { 43 | console.log(err); 44 | }); 45 | }); 46 | }); 47 | }; 48 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Anto", 3 | "identifier" : "sketch.plugin.anto", 4 | "compatibleVersion": 3, 5 | "bundleVersion" : 1, 6 | "icon" : "icon.png", 7 | "commands" : [ 8 | { 9 | "script" : "update.js", 10 | "handlers": { 11 | "actions": { 12 | "Startup" : "update" 13 | } 14 | } 15 | }, 16 | { 17 | "script" : "library.js", 18 | "handlers": { 19 | "actions": { 20 | "Startup" : "cleanLibrary", 21 | "OpenDocument": "addLibrary", 22 | "Shutdown": "cleanLibrary" 23 | } 24 | } 25 | }, 26 | { 27 | "name" : "🔵 Anto", 28 | "icon" : "minicon.png", 29 | "identifier": "AntoPanel", 30 | "script" : "index.js", 31 | "shortcut" : "cmd option a", 32 | "handlers" : { 33 | "actions": { 34 | "run" : "onRun", 35 | "OpenDocument": "onRun" 36 | } 37 | } 38 | } 39 | ], 40 | "menu" : { 41 | "title" : "Anto", 42 | "isRoot": true, 43 | "items" : [ 44 | "AntoPanel" 45 | ] 46 | } 47 | } -------------------------------------------------------------------------------- /src/models/devColor.js: -------------------------------------------------------------------------------- 1 | import Sketch from '../sketch'; 2 | import { join } from 'path'; 3 | import fs from '@skpm/fs'; 4 | import _ from 'lodash'; 5 | import { hexToRgba } from 'hex-and-rgba'; 6 | 7 | const home = require('os').homedir(); 8 | const buildPath = join(home, '.anto'); 9 | 10 | export default class devColor extends Sketch { 11 | constructor() { 12 | super(); 13 | this.namespace = '颜色|devColor'; 14 | } 15 | 16 | run() { 17 | const Tree = {}; 18 | const sortedArtboards = _.sortBy(this.page.layers, ['frame.y', 'frame.x']); 19 | _.forEach(sortedArtboards, artboard => { 20 | if (!Tree[artboard.name]) Tree[artboard.name] = []; 21 | const sortedLayers = _.sortBy(artboard.layers, ['frame.y', 'frame.x']); 22 | _.forEach(sortedLayers, l => { 23 | const color = l.style.fills[0].color; 24 | const rgba = hexToRgba(color); 25 | console.log(rgba); 26 | Tree[artboard.name].push({ 27 | name: l.name, 28 | desc: 29 | rgba[3] === 1 30 | ? color.slice(0, 7) 31 | : `rgba(${rgba[0]},${rgba[1]},${rgba[2]},${rgba[3].toFixed(2)})`, 32 | color: color, 33 | }); 34 | }); 35 | }); 36 | console.log(Tree); 37 | fs.writeFileSync(join(buildPath, 'colors.json'), JSON.stringify(Tree)); 38 | this.openPath(buildPath); 39 | this.ui.success('生产完成'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/models/devNumber.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | 4 | export default class devNumber extends Sketch { 5 | constructor() { 6 | super(); 7 | this.namespace = '序号|devNumber'; 8 | } 9 | run() { 10 | if (this.selection.isEmpty) return this.ui.warn('请选择图层'); 11 | const sortedLayers = _.sortBy(this.selection.layers, ['frame.y', 'frame.x']); 12 | _.forEach(sortedLayers, (layer, index) => { 13 | layer.moveToBack(); 14 | let name = String(layer.name).split('|'); 15 | name.length > 1 ? (name = name[1]) : (name = layer.name); 16 | layer.name = [index, name].join('|'); 17 | }); 18 | this.sortOrder(); 19 | this.ui.success('序号标注成功'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/models/devSymbol.js: -------------------------------------------------------------------------------- 1 | import Sketch from '../sketch'; 2 | import { join } from 'path'; 3 | import fs from '@skpm/fs'; 4 | import _ from 'lodash'; 5 | 6 | const home = require('os').homedir(); 7 | const buildPath = join(home, '.anto'); 8 | 9 | export default class devSymbol extends Sketch { 10 | constructor() { 11 | super(); 12 | this.namespace = '生产|handleBuild'; 13 | } 14 | 15 | run() { 16 | const Tree = {}; 17 | const libBuildPath = join(buildPath, this.fileName); 18 | try { 19 | fs.unlinkSync(libBuildPath); 20 | } catch (e) {} 21 | console.log(libBuildPath); 22 | const sortedArtboards = _.sortBy(this.page.layers, ['frame.y', 'frame.x']); 23 | _.forEach(sortedArtboards, artboard => { 24 | const imgTree = []; 25 | const sortedLayers = _.sortBy(artboard.layers, ['frame.y', 'frame.x']); 26 | _.forEach(sortedLayers, l => { 27 | const data = l.name.split(' / '); 28 | if (!data.length || data.length < 3) return; 29 | if (!Tree[data[0]]) Tree[data[0]] = {}; 30 | // imgTree.push(data); 31 | imgTree.push({ 32 | name: data, 33 | id: l.id, 34 | }); 35 | Tree[data[0]][data[1]] = imgTree; 36 | 37 | this.export(l, { 38 | output: join(libBuildPath, 'img'), 39 | 'use-id-for-name': true, 40 | }); 41 | }); 42 | }); 43 | console.log(Tree); 44 | fs.writeFileSync(join(libBuildPath, 'data.json'), JSON.stringify(Tree)); 45 | this.openPath(libBuildPath); 46 | this.ui.success('生产完成'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/models/devTest.js: -------------------------------------------------------------------------------- 1 | import Sketch from '../sketch'; 2 | import { join } from 'path'; 3 | import fs from '@skpm/fs'; 4 | 5 | const home = require('os').homedir(); 6 | const buildPath = join(home, '.anto'); 7 | 8 | export default class devTest extends Sketch { 9 | constructor() { 10 | super(); 11 | this.namespace = 'Test|devTest'; 12 | } 13 | 14 | run() { 15 | const layer = this.selection.layers[0]; 16 | fs.writeFileSync(join(buildPath, 'test.json'), JSON.stringify(layer)); 17 | this.openPath(buildPath); 18 | this.ui.success('生产完成'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/models/handleBlender.js: -------------------------------------------------------------------------------- 1 | import Sketch from '../sketch'; 2 | 3 | export default class handleBlender extends Sketch { 4 | constructor() { 5 | super(); 6 | this.namespace = '图层混合|handleBlender'; 7 | } 8 | 9 | getColor(selectionType, layer) { 10 | let color; 11 | 12 | if (selectionType === 'fill') { 13 | if (layer instanceof MSTextLayer) { 14 | color = layer.textColor(); 15 | } else { 16 | color = layer 17 | .style() 18 | .fills() 19 | .firstObject() 20 | .color(); 21 | } 22 | } else { 23 | if (layer instanceof MSTextLayer) { 24 | color = layer 25 | .style() 26 | .borders() 27 | .firstObject() 28 | .color(); 29 | } else { 30 | color = layer 31 | .style() 32 | .borders() 33 | .firstObject() 34 | .color(); 35 | } 36 | } 37 | return color; 38 | } 39 | 40 | getNum(num1, num2, index, count) { 41 | return ((num2 - num1) / (count - 1)) * index + num1; 42 | } 43 | 44 | handleStep(steps) { 45 | const selectedLayers = this.native.selection.layers(); 46 | const selectedCount = selectedLayers.length; 47 | 48 | if (selectedCount !== 0) { 49 | const layerColor = new Array(selectedCount); 50 | const layerBorderColor = new Array(selectedCount); 51 | const layerPosX = new Array(selectedCount); 52 | const layerPosY = new Array(selectedCount); 53 | const layerW = new Array(selectedCount); 54 | const layerH = new Array(selectedCount); 55 | const layerRadius = new Array(selectedCount); 56 | const layerOpacity = new Array(selectedCount); 57 | const layerRotation = new Array(selectedCount); 58 | 59 | // for font 60 | const layerFontSize = new Array(selectedCount); 61 | const layerCharacterSpacing = new Array(selectedCount); 62 | const layerLineheight = new Array(selectedCount); 63 | 64 | const copiedLayers = new Array(selectedCount - 1); 65 | 66 | for (let id = 0; id < selectedCount; id++) { 67 | // init array 68 | layerColor[id] = this.getColor('fill', selectedLayers[id]); 69 | 70 | layerPosX[id] = selectedLayers[id].rect().origin.x; 71 | layerPosY[id] = selectedLayers[id].rect().origin.y; 72 | layerW[id] = selectedLayers[id].rect().size.width; 73 | layerH[id] = selectedLayers[id].rect().size.height; 74 | layerOpacity[id] = selectedLayers[id] 75 | .style() 76 | .contextSettings() 77 | .opacity(); 78 | layerRotation[id] = selectedLayers[id].rotation(); 79 | 80 | // for font 81 | if (selectedLayers[id].className() === 'MSTextLayer') { 82 | layerFontSize[id] = selectedLayers[id].fontSize(); 83 | layerCharacterSpacing[id] = selectedLayers[id].characterSpacing(); 84 | layerLineheight[id] = selectedLayers[id].lineHeight(); 85 | } else { 86 | // for rect radius 87 | // for borders 88 | layerBorderColor[id] = this.getColor('border', selectedLayers[id]); 89 | const shape = selectedLayers[id]; 90 | if (shape && shape.isKindOfClass(MSRectangleShape)) { 91 | layerRadius[id] = selectedLayers[id].cornerRadiusFloat(); 92 | } 93 | } 94 | } 95 | 96 | // duplicate layers 97 | for (let id = 0; id < selectedCount - 1; id++) { 98 | copiedLayers[id] = []; 99 | copiedLayers[id].push(selectedLayers[id]); 100 | copiedLayers[id].push(selectedLayers[id + 1]); 101 | let layerRec = selectedLayers[id]; 102 | 103 | for (let k = 0; k < steps; k++) { 104 | const newLayer = layerRec.duplicate(); 105 | newLayer.select_byExpandingSelection(true, true); 106 | newLayer.setName(k.toString()); 107 | copiedLayers[id].splice(k + 1, 0, newLayer); 108 | layerRec = newLayer; 109 | } 110 | } 111 | 112 | for (let id = 0; id < selectedCount - 1; id++) { 113 | // change their styles 114 | for (let i = 0; i < steps + 2; i++) { 115 | const layer = copiedLayers[id][i]; 116 | 117 | // color 118 | const r = Math.round( 119 | this.getNum(layerColor[id].red(), layerColor[id + 1].red(), i, steps + 2) * 255 120 | ); 121 | const g = Math.round( 122 | this.getNum(layerColor[id].green(), layerColor[id + 1].green(), i, steps + 2) * 255 123 | ); 124 | const b = Math.round( 125 | this.getNum(layerColor[id].blue(), layerColor[id + 1].blue(), i, steps + 2) * 255 126 | ); 127 | const a = Math.round( 128 | this.getNum(layerColor[id].alpha(), layerColor[id + 1].alpha(), i, steps + 2) * 255 129 | ); 130 | 131 | // position 132 | const x = Math.round(this.getNum(layerPosX[id], layerPosX[id + 1], i, steps + 2)); 133 | const y = Math.round(this.getNum(layerPosY[id], layerPosY[id + 1], i, steps + 2)); 134 | 135 | // width & height 136 | const w = Math.round(this.getNum(layerW[id], layerW[id + 1], i, steps + 2)); 137 | const h = Math.round(this.getNum(layerH[id], layerH[id + 1], i, steps + 2)); 138 | 139 | // opacity 140 | const op = this.getNum(layerOpacity[id], layerOpacity[id + 1], i, steps + 2); 141 | 142 | // rotation 143 | const rot = this.getNum(layerRotation[id], layerRotation[id + 1], i, steps + 2); 144 | layer.setRotation(rot); 145 | 146 | // set position and width height 147 | layer.rect = NSMakeRect(x, y, w, h); 148 | layer 149 | .style() 150 | .contextSettings() 151 | .setOpacity(op); 152 | 153 | if (selectedLayers[id].className() === 'MSTextLayer') { 154 | const fs = this.getNum(layerFontSize[id], layerFontSize[id + 1], i, steps + 2); 155 | const cs = this.getNum( 156 | layerCharacterSpacing[id], 157 | layerCharacterSpacing[id + 1], 158 | i, 159 | steps + 2 160 | ); 161 | const lh = this.getNum(layerLineheight[id], layerLineheight[id + 1], i, steps + 2); 162 | 163 | layer.setFontSize(fs); 164 | layer.setCharacterSpacing(cs); 165 | layer.setLineHeight(lh); 166 | layer.textColor = MSColor.colorWithRed_green_blue_alpha( 167 | r / 255, 168 | g / 255, 169 | b / 255, 170 | a / 255 171 | ); 172 | } else { 173 | const fill = layer 174 | .style() 175 | .fills() 176 | .firstObject(); 177 | const br = Math.round( 178 | this.getNum( 179 | layerBorderColor[id].red(), 180 | layerBorderColor[id + 1].red(), 181 | i, 182 | steps + 2 183 | ) * 255 184 | ); 185 | const bg = Math.round( 186 | this.getNum( 187 | layerBorderColor[id].green(), 188 | layerBorderColor[id + 1].green(), 189 | i, 190 | steps + 2 191 | ) * 255 192 | ); 193 | const bb = Math.round( 194 | this.getNum( 195 | layerBorderColor[id].blue(), 196 | layerBorderColor[id + 1].blue(), 197 | i, 198 | steps + 2 199 | ) * 255 200 | ); 201 | const ba = Math.round( 202 | this.getNum( 203 | layerBorderColor[id].alpha(), 204 | layerBorderColor[id + 1].alpha(), 205 | i, 206 | steps + 2 207 | ) * 255 208 | ); 209 | 210 | const border = layer 211 | .style() 212 | .borders() 213 | .firstObject(); 214 | border.color = MSColor.colorWithRed_green_blue_alpha( 215 | br / 255, 216 | bg / 255, 217 | bb / 255, 218 | ba / 255 219 | ); 220 | 221 | const shape = selectedLayers[id]; 222 | fill.color = MSColor.colorWithRed_green_blue_alpha(r / 255, g / 255, b / 255, a / 255); 223 | if (shape && shape.isKindOfClass(MSRectangleShape)) { 224 | layer.cornerRadiusFloat = Math.round( 225 | this.getNum(parseInt(layerRadius[id]), parseInt(layerRadius[id + 1]), i, steps + 2) 226 | ); 227 | } 228 | } 229 | } 230 | } 231 | } 232 | } 233 | 234 | run() { 235 | if (this.selection.length < 2) return this.ui.warn('请选择两个以上图层'); 236 | this.ui.inputPanel('请输入歩进数', { initialValue: 10 }, (err, value) => { 237 | if (err) return; 238 | if (parseInt(value) < 1) return this.ui.warn('请输入大于零的数字'); 239 | this.handleStep(parseInt(value)); 240 | this.ui.success(`图层混合完毕`); 241 | }); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/models/handleBuildLocalSymbol.js: -------------------------------------------------------------------------------- 1 | import fs from '@skpm/fs'; 2 | import _ from 'lodash'; 3 | import Sketch from '../sketch'; 4 | import { join } from 'path'; 5 | 6 | export default class handleBuildLocalSymbol extends Sketch { 7 | constructor() { 8 | super(); 9 | this.namespace = '本地组件预览|handleBuildLocalSymbol'; 10 | } 11 | 12 | build() { 13 | console.log('[start]', this.namespace); 14 | const data = []; 15 | const home = require('os').homedir(); 16 | const path = join(home, 'localSymbols'); 17 | try { 18 | fs.unlinkSync(path); 19 | } catch (e) {} 20 | _.forEach(this.symbols, symbol => { 21 | this.export(symbol, { 22 | overwriting: true, 23 | 'use-id-for-name': true, 24 | output: path, 25 | scales: '0.5', 26 | formats: 'jpg', 27 | compression: 0.5, 28 | }); 29 | }); 30 | 31 | _.forEach(this.symbols, symbol => { 32 | let img = fs.readFileSync(join(path, `${symbol.id}@0.5x.jpg`)); 33 | img = new Buffer(img).toString('base64'); 34 | const base64 = 'data:jpg;base64,' + img; 35 | data.push({ 36 | name: symbol.name, 37 | id: symbol.id, 38 | path: base64, 39 | }); 40 | }); 41 | console.log('[end]', this.namespace); 42 | this.ui.success('本地组件刷新成功'); 43 | return { 44 | time: String(new Date()), 45 | data: data, 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/models/handleChange.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | 4 | export default class handleChange extends Sketch { 5 | constructor() { 6 | super(); 7 | this.namespace = '变向|handleChange'; 8 | } 9 | 10 | run() { 11 | if (this.selection.isEmpty) return this.ui.warn('请选择线条'); 12 | _.forEach(this.selection.layers, l => { 13 | if (l.type !== 'ShapePath') return; 14 | const Start = l.style.borderOptions.startArrowhead; 15 | const End = l.style.borderOptions.endArrowhead; 16 | l.style.borderOptions.startArrowhead = End; 17 | l.style.borderOptions.endArrowhead = Start; 18 | }); 19 | this.ui.success('变向成功'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/models/handleColor.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | 4 | export default class handleColor extends Sketch { 5 | constructor() { 6 | super(); 7 | this.namespace = '色板|handleColor'; 8 | } 9 | 10 | shapeBorder(layer, fill) { 11 | const oldStyle = _.assign(layer.style.borders[0]); 12 | layer.style.borders = [_.assign(oldStyle, fill)]; 13 | } 14 | 15 | shapeFill(layer, fill) { 16 | layer.style.fills = [fill]; 17 | } 18 | 19 | textColor(layer, fill) { 20 | layer.style.textColor = fill.color; 21 | layer.style.fills = []; 22 | } 23 | 24 | run(e) { 25 | if (this.selection.isEmpty) return this.ui.warn('请选择图形'); 26 | const { border, color } = JSON.parse(e); 27 | const fill = { 28 | fillType: 'Color', 29 | color: color, 30 | }; 31 | _.forEach(this.selection.layers, layer => { 32 | if (!layer.type) return; 33 | if (layer.type === 'ShapePath') { 34 | border ? this.shapeBorder(layer, fill) : this.shapeFill(layer, fill); 35 | } 36 | if (layer.type === 'Text') { 37 | this.textColor(layer, fill); 38 | } 39 | }); 40 | 41 | this.ui.success(`${border ? '描边' : '填充'}「${fill.color}」`); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/models/handleDash.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | 4 | export default class handleDash extends Sketch { 5 | constructor() { 6 | super(); 7 | this.namespace = '虚实|handleDash'; 8 | } 9 | 10 | run() { 11 | if (this.selection.isEmpty) return this.ui.warn('请选择线条'); 12 | _.forEach(this.selection.layers, l => { 13 | if (l.type !== 'ShapePath') return; 14 | const dash = l.style.borderOptions.dashPattern; 15 | if (!dash || dash.length === 0 || dash.toString() === [0, 0, 0, 0].toString()) { 16 | l.style.borderOptions.dashPattern = [4, 6, 4, 6]; 17 | this.ui.success('变成虚线'); 18 | } else { 19 | l.style.borderOptions.dashPattern = [0, 0, 0, 0]; 20 | this.ui.success('变成实线'); 21 | } 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/models/handleExport.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | import moment from 'moment'; 4 | import { join } from 'path'; 5 | import preivew from '../preview.json'; 6 | import fs from '@skpm/fs'; 7 | import dialog from '@skpm/dialog'; 8 | import jsZip from 'jszip'; 9 | 10 | export default class handleExport extends Sketch { 11 | constructor() { 12 | super(); 13 | this.namespace = '导出|handleExport'; 14 | this.height = 400; 15 | } 16 | 17 | run() { 18 | const zip = new jsZip(); 19 | const Data = { 20 | date: moment().format('YYYY-MM-DD'), 21 | author: this.setting.get('config-name'), 22 | pages: [], 23 | }; 24 | 25 | // 获取路径 26 | const RootPath = dialog.showSaveDialog({ 27 | title: 'Export Zip to', 28 | defaultPath: [this.fileName, moment().format('MMDD')].join(' '), 29 | }); 30 | 31 | if (_.isUndefined(RootPath)) return; 32 | 33 | _.forEach(this.native.pages, page => { 34 | if (String(page.name())[0] === '@') return; 35 | _.forEach(page.layers(), layer => { 36 | if (String(layer.name()) === '@制版') { 37 | const sliceLayer = _.filter(layer.layers(), l => String(l.class()) === 'MSSliceLayer')[0]; 38 | const name = String(sliceLayer.name()); 39 | const info = name.split(' (')[1].split(') '); 40 | const path = join('preview', name + '.jpg'); 41 | this.native.setY(sliceLayer, sliceLayer.frame().y() + this.height); 42 | this.native.setHeight(sliceLayer, sliceLayer.frame().height() - this.height); 43 | 44 | // 设置封面 45 | const symbol = _.filter(layer.layers(), l => String(l.class()) === 'MSSymbolInstance')[0]; 46 | const symbolName = name + '-cover'; 47 | const symbolSlice = this.native.setSlice(symbol, symbolName, '200', 'w'); 48 | this.native.addLayers(layer, symbolSlice); 49 | this.native.setX(symbolSlice, sliceLayer.frame().x()); 50 | this.native.setY(symbolSlice, sliceLayer.frame().y()); 51 | this.native.setWidth(symbolSlice, sliceLayer.frame().width()); 52 | this.native.setHeight(symbolSlice, sliceLayer.frame().height()); 53 | 54 | // 导出切图 55 | this.native.exportSlice(sliceLayer, join(RootPath, 'preview'), name); 56 | this.native.setY(sliceLayer, sliceLayer.frame().y() - this.height); 57 | this.native.setHeight(sliceLayer, sliceLayer.frame().height() + this.height); 58 | 59 | // 导出封面 60 | this.native.exportSlice(symbolSlice, join(RootPath, 'preview'), symbolName); 61 | const coverPath = join('preview', symbolName + '.jpg'); 62 | this.native.remove(symbolSlice); 63 | 64 | // 添加压缩包 65 | zip.file(path, fs.readFileSync(join(RootPath, path))); 66 | zip.file(coverPath, fs.readFileSync(join(RootPath, coverPath))); 67 | 68 | // 添加数据 69 | Data.pages.push({ 70 | path: path, 71 | cover: coverPath, 72 | name: String(page.name()), 73 | mode: info[0], 74 | date: info[1], 75 | width: sliceLayer.frame().width(), 76 | height: sliceLayer.frame().height() - this.height, 77 | }); 78 | } 79 | }); 80 | }); 81 | 82 | // 导出文件添加压缩包 83 | zip.file('data.js', ` localStorage.setItem('preview', '${JSON.stringify(Data)}');`); 84 | zip.file('index.css', preivew.css); 85 | zip.file('index.html', preivew.html); 86 | zip.file('index.js', preivew.js); 87 | 88 | // 删除缓存图片 89 | fs.unlinkSync(RootPath); 90 | 91 | // 创建压缩包 92 | this.ui.message(`⏲ 开始生产压缩文件,请稍后...`); 93 | zip.generateAsync({ type: 'base64' }).then(data => { 94 | const file = Buffer.from(data, 'base64'); 95 | const filePath = RootPath + '.zip'; 96 | fs.writeFileSync(filePath, file); 97 | this.ui.alert('✅ 导出成功', `导出至:${filePath}`); 98 | this.openPath(join(filePath, '..')); 99 | }); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/models/handleFrontBack.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | 4 | export default class handleFrontBack extends Sketch { 5 | constructor() { 6 | super(); 7 | this.namespace = '大置顶大置底|handleFrontBack'; 8 | } 9 | 10 | run(name) { 11 | const GroupName = `@${name}`; 12 | const Groups = this.layer.get(this.page, GroupName); 13 | const Group = 14 | Groups[0] || 15 | this.create.group({ 16 | name: GroupName, 17 | parent: this.page, 18 | }); 19 | 20 | const layers = _.filter( 21 | this.selection.layers, 22 | l => l.type && l.type !== 'Page' && l.type !== 'Artboard' 23 | ); 24 | if (layers.length === 0) return this.ui.warn(`请选择需要${name}的图层`); 25 | this.setGroup(Group, layers); 26 | Group.locked = true; 27 | this.sortOrder(); 28 | this.ui.success(`所选图层已${name}`); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/models/handleFrontBackLite.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | 4 | export default class handleFrontBackLite extends Sketch { 5 | constructor() { 6 | super(); 7 | this.namespace = '置顶置底|handleFrontBackLite'; 8 | } 9 | 10 | run(name) { 11 | if (this.selection.isEmpty) return this.ui.warn('请选择图形'); 12 | 13 | _.forEach(this.selection.layers, layer => { 14 | if (name === '置顶') { 15 | if (this.isTop(layer) && !this.needBreak(layer)) this.changeParent(layer); 16 | layer.moveToFront(); 17 | } else { 18 | if (this.isBottom(layer) && !this.needBreak(layer)) this.changeParent(layer); 19 | layer.moveToBack(); 20 | } 21 | }); 22 | 23 | this.sortOrder(); 24 | this.ui.success(`所选图层已${name}`); 25 | } 26 | 27 | needBreak(layer) { 28 | return layer.parent.type === 'Page' || layer.parent.type === 'Artboard'; 29 | } 30 | 31 | changeParent(layer) { 32 | this.changeBasis(layer, { from: layer.parent, to: layer.parent.parent }); 33 | layer.parent = layer.parent.parent; 34 | } 35 | 36 | isTop(layer) { 37 | const Parent = layer.parent; 38 | return layer.id === Parent.layers[Parent.layers.length - 1].id; 39 | } 40 | 41 | isBottom(layer) { 42 | const Parent = layer.parent; 43 | return layer.id === Parent.layers[0].id; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/models/handleHeight.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | 4 | export default class handleHeight extends Sketch { 5 | constructor() { 6 | super(); 7 | this.namespace = '调高|handleHeight'; 8 | } 9 | 10 | run() { 11 | if (this.selection.isEmpty) return this.ui.warn('请选择画板或图层'); 12 | if (this.selection.layers.length > 1) return this.ui.warn('请选择一个画板或图层'); 13 | // 找画板 14 | let artboard; 15 | if (this.selection.layers[0].type !== 'Artboard') { 16 | artboard = this.layer.getArtboard(this.selection.layers[0]); 17 | if (!artboard) return this.ui.warn('请选择一个画板或图层'); 18 | } else { 19 | artboard = this.selection.layers[0]; 20 | } 21 | // 计算高度 22 | let maxHeight = 0; 23 | _.forEach(artboard.layers, l => { 24 | const height = l.frame.y + l.frame.height; 25 | if (height > maxHeight) maxHeight = height; 26 | }); 27 | artboard.frame.height = maxHeight; 28 | 29 | this.ui.success('高度已适配'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/models/handleIgnore.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | 4 | export default class handleIgnore extends Sketch { 5 | constructor() { 6 | super(); 7 | this.namespace = '排除|handleIgnore'; 8 | } 9 | 10 | run() { 11 | if (this.selection.isEmpty) return this.ui.warn('请选择图形'); 12 | 13 | const GlobalStatus = this.isIgnore(this.selection.layers[0]); 14 | 15 | _.forEach(this.selection.layers, layer => { 16 | if (!layer.name) return; 17 | GlobalStatus ? this.setActive(layer) : this.setIgnore(layer); 18 | }); 19 | 20 | this.ui.success(GlobalStatus ? '「激活」成功' : '「排除」成功'); 21 | } 22 | 23 | isIgnore(layer) { 24 | return layer.name ? layer.name[0] === '@' : false; 25 | } 26 | 27 | setActive(layer) { 28 | layer.name = layer.name.replace(/^@/g, ''); 29 | if (this.isIgnore(layer)) this.setActive(layer); 30 | } 31 | 32 | setIgnore(layer) { 33 | this.setActive(layer); 34 | layer.name = '@' + layer.name; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/models/handleLayout.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | 4 | export default class handleLayout extends Sketch { 5 | constructor() { 6 | super(); 7 | this.namespace = '对齐|handleLayout'; 8 | this.option = { 9 | marginX: 100, 10 | marginY: 300, 11 | }; 12 | } 13 | 14 | run() { 15 | const sortedArtboards = _.sortBy(this.artboards, ['frame.y', 'frame.x']); 16 | 17 | const snapDistance = 18 | sortedArtboards.reduce((initial, artboard) => { 19 | initial += artboard.frame.height; 20 | return initial; 21 | }, 0) / sortedArtboards.length; 22 | 23 | sortedArtboards.forEach(artboard => { 24 | artboard.frame.y = this.snapValueToGrid(artboard.frame.y, snapDistance); 25 | }); 26 | 27 | const artboardRows = sortedArtboards.reduce((initial, artboard) => { 28 | initial.push(artboard.frame.y); 29 | return initial; 30 | }, []); 31 | 32 | const baseFrame = sortedArtboards[0].frame; 33 | let artboardY = baseFrame.y; 34 | let currentRow = 0; 35 | 36 | _.uniq(artboardRows).forEach(rowValue => { 37 | let tallestArtboard = 0; 38 | let artboardX = baseFrame.x; 39 | 40 | const artboardsInRow = sortedArtboards.filter(artboard => artboard.frame.y === rowValue); 41 | _.sortBy(artboardsInRow, 'frame.x').forEach(artboard => { 42 | artboard.frame.x = artboardX; 43 | artboard.frame.y = artboardY; 44 | artboardX += artboard.frame.width + this.option.marginX; 45 | if (artboard.frame.height > tallestArtboard) tallestArtboard = artboard.frame.height; 46 | }); 47 | 48 | artboardY += tallestArtboard + this.option.marginY; 49 | 50 | currentRow++; 51 | }); 52 | 53 | this.sortOrder(); 54 | this.ui.success('对齐成功'); 55 | } 56 | 57 | snapValueToGrid(value, grid) { 58 | let div = value / grid; 59 | const rest = div - Math.floor(div); 60 | if (rest > 0.8) { 61 | div += 1; 62 | } 63 | return Math.floor(Math.floor(div) * grid); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/models/handleLine.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | 4 | const GroupName = '@交互连线'; 5 | 6 | export default class handleLine extends Sketch { 7 | constructor() { 8 | super(); 9 | this.namespace = '变向|handleChange'; 10 | this.lineStyle = { 11 | borders: [ 12 | { 13 | color: '#2A72FF', 14 | thickness: 3, 15 | }, 16 | ], 17 | borderOptions: { 18 | endArrowhead: 'FilledArrow', 19 | lineJoin: 'Round', 20 | }, 21 | }; 22 | } 23 | 24 | run() { 25 | if (this.selection.isEmpty) return this.ui.warn('请选择图层'); 26 | if (this.selection.length > 2) return this.ui.warn('不能大于两个图层'); 27 | 28 | // 上色 29 | if (this.selection.length === 1) { 30 | const layer = this.selection.layers[0]; 31 | if (layer.type !== 'ShapePath') return this.ui.warn('单图层时只可选取连线'); 32 | layer.style = this.lineStyle; 33 | return; 34 | } 35 | 36 | // 清除缓存线 37 | const aId = this.selection.layers[0].id; 38 | const bId = this.selection.layers[1].id; 39 | const newName = [this.selection.layers[0].name, this.selection.layers[1].name].join('-'); 40 | const oldLine = this.getLine(aId, bId); 41 | if (oldLine) { 42 | this.lineStyle = oldLine.style; 43 | oldLine.remove(); 44 | } 45 | 46 | // 连线 47 | const path = []; 48 | _.forEach(this.selection.layers, layer => { 49 | const rect = layer.frame; 50 | const newRect = rect.changeBasis({ from: layer.parent }); 51 | const data = { 52 | x: newRect.x, 53 | y: newRect.y, 54 | width: rect.width, 55 | height: rect.height, 56 | }; 57 | data.center = { 58 | x: data.x + data.width / 2, 59 | y: data.y + data.height / 2, 60 | }; 61 | path.push(data); 62 | }); 63 | 64 | // 画线 65 | const linePath = NSBezierPath.bezierPath(); 66 | const newPath = this.handlePath(path); 67 | for (let i = 0; i < newPath.length; i++) { 68 | i === 0 69 | ? linePath.moveToPoint(NSMakePoint(newPath[i].x, newPath[i].y)) 70 | : linePath.lineToPoint(NSMakePoint(newPath[i].x, newPath[i].y)); 71 | } 72 | 73 | // 原生线条 74 | const lineSh = MSShapePathLayer.layerWithPath(MSPath.pathWithBezierPath(linePath)); 75 | const tempName = '@tempLine' + Math.random(); 76 | lineSh.setName(tempName); 77 | this.native.page.addLayers([lineSh]); 78 | 79 | this.selectionClear(); 80 | 81 | const Groups = this.layer.get(this.page, GroupName); 82 | const Group = Groups[0] || this.create.group({ name: GroupName }); 83 | Group.parent = this.page; 84 | Group.locked = true; 85 | 86 | const Line = this.layer.globalGet(this.document, tempName)[0]; 87 | Line.selected = true; 88 | Line.name = newName; 89 | Line.style = this.lineStyle; 90 | this.changeBasis(Line, { from: this.page, to: Group }); 91 | Line.parent = Group; 92 | this.setLine(aId, bId, Line.id); 93 | 94 | this.sortOrder(); 95 | 96 | this.ui.success('连线成功'); 97 | } 98 | 99 | handlePath(path) { 100 | const p1 = path[0]; 101 | const p2 = path[1]; 102 | const pointS = {}; 103 | const pointE = {}; 104 | let point2; 105 | let point3; 106 | 107 | if (Math.abs(p1.center.x - p2.center.x) > (p1.width + p2.width) / 2 && p1.x !== p2.x) { 108 | if (p1.x < p2.x) { 109 | pointS.x = p1.x + p1.width; 110 | pointE.x = p2.x; 111 | } else { 112 | pointS.x = p1.x; 113 | pointE.x = p2.x + p2.width; 114 | } 115 | pointS.y = p1.center.y; 116 | pointE.y = p2.center.y; 117 | point2 = { 118 | x: pointS.x - (pointS.x - pointE.x) / 2, 119 | y: pointS.y, 120 | }; 121 | point3 = { 122 | x: pointS.x - (pointS.x - pointE.x) / 2, 123 | y: pointE.y, 124 | }; 125 | } else { 126 | if (p1.y < p2.y) { 127 | pointS.y = p1.y + p1.height; 128 | pointE.y = p2.y; 129 | } else { 130 | pointS.y = p1.y; 131 | pointE.y = p2.y + p2.height; 132 | } 133 | pointS.x = p1.center.x; 134 | pointE.x = p2.center.x; 135 | point2 = { 136 | x: pointS.x, 137 | y: pointS.y - (pointS.y - pointE.y) / 2, 138 | }; 139 | point3 = { 140 | x: pointE.x, 141 | y: pointS.y - (pointS.y - pointE.y) / 2, 142 | }; 143 | } 144 | 145 | return [pointS, point2, point3, pointE]; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/models/handleLocalSymbol.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | 4 | export default class handleLocalSymbol extends Sketch { 5 | constructor() { 6 | super(); 7 | this.namespace = '本地组件|handleLocalSymbol'; 8 | } 9 | 10 | run(id) { 11 | const master = this.layer.getById(this.document, id); 12 | const instance = master.createNewInstance(); 13 | 14 | // 定位拖入位置 15 | let image = _.filter(this.layer.globalGet(this.document, id), l => l.type === 'Image')[0]; 16 | if (!image) { 17 | image = _.filter(this.selection.layers, l => l.type === 'Image')[0]; 18 | } 19 | 20 | instance.parent = image.parent; 21 | instance.frame.x = image.frame.x; 22 | instance.frame.y = image.frame.y; 23 | image.remove(); 24 | 25 | this.selectionClear(); 26 | this.selectionSet(instance); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/models/handleNote.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | import moment from 'moment'; 4 | 5 | export default class handleNote extends Sketch { 6 | constructor() { 7 | super(); 8 | this.namespace = '标注|handleNote'; 9 | this.option = { 10 | header: { 11 | name: `${this.mode} / 标题-链路`, 12 | }, 13 | subheader: { 14 | name: `${this.mode} / 标题-小标题`, 15 | }, 16 | block: { 17 | name: `${this.mode} / 注释-块`, 18 | }, 19 | list: { 20 | name: `${this.mode} / 注释-列`, 21 | }, 22 | ul: { 23 | name: `${this.mode} / 描述-列表`, 24 | }, 25 | point: { 26 | name: `交互 / 节点-矩形`, 27 | }, 28 | round: { 29 | name: `交互 / 节点-胶囊`, 30 | }, 31 | if: { 32 | name: `交互 / 节点-判断`, 33 | }, 34 | changelog: { 35 | name: `${this.mode} / 描述-变更记录`, 36 | replace: '日期', 37 | }, 38 | }; 39 | } 40 | 41 | run(type) { 42 | const isText = type === 'text'; 43 | if (this.selection.isEmpty) return this.ui.warn('请选择文本'); 44 | const texts = _.filter( 45 | this.selection.layers, 46 | l => l.type === 'Text' || l.type === 'SymbolInstance' 47 | ); 48 | if (texts.length === 0) return this.ui.warn('请选择文本'); 49 | 50 | // 找到Symbol 51 | let master; 52 | if (!isText) { 53 | master = this.library.getSymbol(this.library.antoExport, this.option[type].name); 54 | if (!master) return this.ui.warn('请检查Symbol是否存在'); 55 | } 56 | 57 | this.selectionClear(); 58 | 59 | _.forEach(texts, text => { 60 | if (!text.type) return this.ui.warn('请选择文本'); 61 | if (isText) { 62 | if (text.type === 'Text') { 63 | this.setStyle(text); 64 | } else { 65 | const newText = this.create.text({ 66 | name: text.name, 67 | frame: text.frame, 68 | parent: text.parent, 69 | }); 70 | this.setStyle(newText); 71 | try { 72 | const value = _.filter( 73 | text.overrides, 74 | o => !o.isDefault && o.property === 'stringValue' 75 | )[0].value; 76 | newText.text = value; 77 | text.remove(); 78 | } catch (e) {} 79 | } 80 | } else { 81 | const symbolMaster = master.import(); 82 | const instance = symbolMaster.createNewInstance(); 83 | instance.name = text.name; 84 | instance.parent = text.parent; 85 | instance.frame.x = text.frame.x; 86 | instance.frame.y = text.frame.y; 87 | instance.selected = true; 88 | 89 | // 设置override 90 | if (text.type === 'Text') { 91 | this.setByValue(instance, '文字', text.text); 92 | } else { 93 | _.forEach(text.overrides, o => { 94 | if (!o.isDefault && o.property === 'stringValue') 95 | this.setByValue(instance, '文字', o.value); 96 | }); 97 | } 98 | if (type === 'changelog') 99 | this.setByValue(instance, this.option.changelog.replace, moment().format('MMDD')); 100 | text.remove(); 101 | } 102 | }); 103 | 104 | this.ui.success('标注成功'); 105 | } 106 | 107 | setStyle(layer) { 108 | layer.systemFontSize = 32; 109 | layer.frame.width = 750; 110 | layer.alignment = 'justify'; 111 | layer.selected = true; 112 | layer.style = { 113 | opacity: 1, 114 | borders: [], 115 | shadows: [], 116 | fills: [ 117 | { 118 | color: this.mode === '交互' ? '#333' : '#ffffff', 119 | fill: 'Color', 120 | }, 121 | ], 122 | }; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/models/handlePlate.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | import moment from 'moment'; 4 | 5 | const GroupName = '@制版'; 6 | const SubGroupName = '@画板投影'; 7 | 8 | export default class handlePlate extends Sketch { 9 | constructor() { 10 | super(); 11 | this.namespace = '制版|handlePlate'; 12 | this.padding = 400; 13 | } 14 | 15 | run() { 16 | const isInteractiveMode = this.mode === '交互'; 17 | // 找到Symbol 18 | const master = this.library.getSymbol(this.library.antoExport, `${this.mode} / 画板`); 19 | if (!master) return this.ui.warn('请检查Symbol是否存在'); 20 | const symbolMaster = master.import(); 21 | 22 | // 清旧图层组 23 | this.layer.deepRemove(this.page, GroupName); 24 | this.layer.deepRemove(this.page, SubGroupName); 25 | 26 | // 框选画板>1个时只制版框选部分 27 | let Layers; 28 | if (_.filter(this.selection.layers, l => l.type && l.type === 'Artboard').length > 1) { 29 | Layers = this.selection; 30 | } else { 31 | Layers = _.filter(this.page.layers, layer => layer.name[0] !== '@'); 32 | } 33 | if (Layers.length === 0) return this.ui.warn('找不到可用画板'); 34 | 35 | // 遍历 36 | let x = Infinity; 37 | let y = Infinity; 38 | let x2 = -Infinity; 39 | let y2 = -Infinity; 40 | 41 | let ShadowGroup; 42 | if (isInteractiveMode) 43 | ShadowGroup = this.create.group({ 44 | name: SubGroupName, 45 | parent: this.page, 46 | locked: true, 47 | }); 48 | 49 | Layers.forEach(l => { 50 | const rect = l.frame; 51 | if (rect.x < x) x = rect.x; 52 | if (rect.y < y) y = rect.y; 53 | if (rect.x + rect.width > x2) x2 = rect.x + rect.width; 54 | if (rect.y + rect.height > y2) y2 = rect.y + rect.height; 55 | // 画投影 56 | if (isInteractiveMode && l.type === 'Artboard') { 57 | const Shadow = this.create.shape({ 58 | name: l.name, 59 | frame: l.frame, 60 | style: { 61 | fills: [ 62 | { 63 | color: '#ffffff', 64 | fill: 'Color', 65 | }, 66 | ], 67 | borders: [], 68 | shadows: [ 69 | { 70 | color: '#00000022', 71 | y: 40, 72 | blur: 100, 73 | spread: -20, 74 | }, 75 | ], 76 | }, 77 | }); 78 | this.changeBasis(Shadow, { from: this.page, to: ShadowGroup }); 79 | Shadow.parent = ShadowGroup; 80 | } 81 | }); 82 | 83 | // 制版 84 | const instance = symbolMaster.createNewInstance(); 85 | instance.frame.x = x - this.padding; 86 | instance.frame.y = y - this.padding * 2.1; 87 | instance.frame.width = x2 - x + 2 * this.padding; 88 | instance.frame.height = y2 - y + 3.7 * this.padding; 89 | instance.parent = this.page; 90 | instance.locked = true; 91 | 92 | // 设置override-title 93 | const author = this.setting.get('config-name'); 94 | if (author && author.length > 0) this.setByValue(instance, '花名', author); 95 | const title = this.page.name + ` (${this.mode})`; 96 | this.setByValue(instance, '标题', title); 97 | this.setByValue(instance, '日期', moment().format('YYYY-MM-DD')); 98 | const Group = this.create.group({ 99 | name: GroupName, 100 | parent: this.page, 101 | locked: true, 102 | }); 103 | 104 | this.changeBasis(instance, { from: this.page, to: Group }); 105 | instance.parent = Group; 106 | 107 | // 原生切片 108 | const sliceLayer = this.create.slice({ 109 | name: [title, moment().format('MMDD')].join(' '), 110 | frame: instance.frame, 111 | parent: Group, 112 | exportFormats: [{ size: isInteractiveMode ? '0.5x' : '1x' }], 113 | }); 114 | this.selectionClear(); 115 | sliceLayer.selected = true; 116 | this.sortOrder(); 117 | this.ui.success('制版成功'); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/models/handleSort.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | 4 | export default class handleSort extends Sketch { 5 | constructor() { 6 | super(); 7 | this.namespace = '排序|handleSort'; 8 | } 9 | run() { 10 | const sortedArtboards = _.sortBy(this.artboards, ['frame.y', 'frame.x']); 11 | _.forEach(sortedArtboards, layer => layer.moveToBack()); 12 | this.sortOrder(); 13 | this.ui.success('排序成功'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/models/handleSymbol.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | 4 | export default class handlSymbol extends Sketch { 5 | constructor() { 6 | super(); 7 | this.namespace = '组件|handlSymbol'; 8 | } 9 | 10 | run(e) { 11 | const data = JSON.parse(e); 12 | 13 | // 找到Library 14 | const library = this.library.get(data.libname); 15 | const libDocument = library.getDocument(); 16 | const symbol = libDocument.getLayerWithID(data.id); 17 | 18 | // 制作临时组件 19 | const copySymbol = symbol.duplicate(); 20 | const name = ['✱', 'Temp', copySymbol.name].join(' / '); 21 | const masterArtboard = this.create.artboard({ 22 | name: name, 23 | frame: copySymbol.frame, 24 | parent: libDocument.pages[1], 25 | layers: [], 26 | flowStartPoint: true, 27 | }); 28 | copySymbol.frame.x = 0; 29 | copySymbol.frame.y = 0; 30 | copySymbol.parent = masterArtboard; 31 | const tempMaster = this.create.symbolMaster(masterArtboard); 32 | 33 | // 引入 34 | const master = this.library.getSymbol(library, name); 35 | if (!master) return this.ui.warn('请检查Symbol是否存在'); 36 | const symbolMaster = master.import(); 37 | const instance = symbolMaster.createNewInstance(); 38 | instance.parent = this.page; 39 | const group = instance.detach(); 40 | const inner = group.name[0] !== '✱' ? group.duplicate() : group.layers[0]; 41 | group.remove(); 42 | tempMaster.remove(); 43 | masterArtboard.remove(); 44 | 45 | // 定位拖入位置 46 | let image = _.filter( 47 | this.layer.globalGet(this.document, data.name), 48 | l => l.type === 'Image' 49 | )[0]; 50 | if (!image) { 51 | image = _.filter(this.selection.layers, l => l.type === 'Image')[0]; 52 | } 53 | if (image) { 54 | inner.parent = image.parent; 55 | inner.frame.x = image.frame.x; 56 | inner.frame.y = image.frame.y; 57 | image.remove(); 58 | } else { 59 | inner.parent = group.parent; 60 | inner.frame.x = group.frame.x; 61 | inner.frame.y = group.frame.y; 62 | } 63 | 64 | this.selectionClear(); 65 | this.selectionSet(inner); 66 | 67 | if (inner.layers) { 68 | _.forEach(inner.layers, l => { 69 | if (l.name === '@Bg') { 70 | l.remove(); 71 | } 72 | }); 73 | if (inner.layers.length === 1) { 74 | const innerChild = inner.layers[0]; 75 | innerChild.parent = inner.parent; 76 | this.changeBasis(innerChild, { from: inner, to: inner.parent }); 77 | innerChild.selected = true; 78 | inner.remove(); 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/models/handleTitle.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Sketch from '../sketch'; 3 | 4 | const GroupName = '@画板标题'; 5 | 6 | export default class handleTitle extends Sketch { 7 | constructor() { 8 | super(); 9 | this.namespace = '制标|handleTitle'; 10 | } 11 | 12 | run() { 13 | const Artboards = _.filter( 14 | this.artboards, 15 | layer => layer.name[0] !== '@' && layer.frame.width >= 750 / 3 && layer.frame.width <= 750 * 3 16 | ); 17 | if (Artboards.length === 0) return this.ui.warn('找不到可用画板'); 18 | 19 | // 找到Symbol 20 | let symbolName; 21 | if (this.mode === '视觉') { 22 | symbolName = '视觉 / 标题'; 23 | } else { 24 | const titleStyle = this.setting.get('config-title'); 25 | symbolName = titleStyle === 'strong' ? '交互 / 标题-强' : '交互 / 标题'; 26 | } 27 | 28 | const master = this.library.getSymbol(this.library.antoExport, symbolName); 29 | if (!master) return this.ui.warn('请检查Symbol是否存在'); 30 | 31 | // 导入 32 | this.layer.deepRemove(this.page, GroupName); 33 | const Group = this.create.group({ 34 | name: GroupName, 35 | parent: this.page, 36 | }); 37 | const symbolMaster = master.import(); 38 | 39 | // 生成 40 | _.forEach(Artboards, Artboard => { 41 | const instance = symbolMaster.createNewInstance(); 42 | instance.parent = Group; 43 | instance.frame.x = Artboard.frame.x; 44 | instance.frame.y = Artboard.frame.y - 150; 45 | instance.frame.width = Artboard.frame.width; 46 | this.changeBasis(instance, { from: this.page, to: Group }); 47 | // 设置override 48 | const titleId = '标题'; 49 | this.setByValue(instance, titleId, Artboard.name); 50 | }); 51 | 52 | // 锁定 53 | Group.locked = true; 54 | this.sortOrder(); 55 | this.ui.success('制标成功'); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | identifier: 'anto.tools', 3 | width: 48, 4 | height: 700, 5 | alwaysOnTop: true, 6 | minimizable: false, 7 | maximizable: false, 8 | fullscreenable: false, 9 | closable: false, 10 | titleBarStyle: 'hidden', 11 | vibrancy: 'sidebar', 12 | resizable: false, 13 | show: false, 14 | frame: false, 15 | transparent: true, 16 | }; 17 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Options from './options'; 3 | import Sketch from './sketch'; 4 | // 组件 5 | import handleSymbol from './models/handleSymbol'; 6 | import handleLocalSymbol from './models/handleLocalSymbol'; 7 | import handleBuildLocalSymbol from './models/handleBuildLocalSymbol'; 8 | // 色板 9 | import handleColor from './models/handleColor'; 10 | // 注释 11 | import handleNote from './models/handleNote'; 12 | // 连线 13 | import handleLine from './models/handleLine'; 14 | import handleDash from './models/handleDash'; 15 | import handleChange from './models/handleChange'; 16 | // 图层 17 | import handleFrontBackLite from './models/handleFrontBackLite'; 18 | import handleFrontBack from './models/handleFrontBack'; 19 | import handleSort from './models/handleSort'; 20 | import handleLayout from './models/handleLayout'; 21 | import handleHeight from './models/handleHeight'; 22 | import handleBlender from './models/handleBlender'; 23 | // 制版 24 | import handleIgnore from './models/handleIgnore'; 25 | import handleTitle from './models/handleTitle'; 26 | import handlePlate from './models/handlePlate'; 27 | import handleExport from './models/handleExport'; 28 | // 开发 29 | import devSymbol from './models/devSymbol'; 30 | import devColor from './models/devColor'; 31 | import devNumber from './models/devNumber'; 32 | import devTest from './models/devTest'; 33 | 34 | export default class Router extends Sketch { 35 | constructor(browserWindow) { 36 | super(); 37 | this.namespace = '路由|Router'; 38 | this.browserWindow = browserWindow; 39 | this.webContents = browserWindow.webContents; 40 | this.width = Options.width; 41 | this.height = Options.height; 42 | } 43 | 44 | sendWebview(key, data) { 45 | this.browserWindow.webContents.executeJavaScript( 46 | `localStorage.setItem("${key}",'${JSON.stringify(data)}')` 47 | ); 48 | } 49 | 50 | panel() { 51 | this.webContents.on('openPanel', e => 52 | this.browserWindow.setSize(e ? this.width + e : this.width * 2, this.height) 53 | ); 54 | this.webContents.on('closePanel', () => 55 | this.browserWindow.setSize(this.width, this.height, true) 56 | ); 57 | } 58 | 59 | symbol() { 60 | this.webContents.on('handleSymbol', e => new handleSymbol().start(e)); 61 | this.webContents.on('handleLocalSymbol', e => new handleLocalSymbol().start(e)); 62 | this.webContents.on('handleRule', e => { 63 | let url = 'https://yuque.antfin-inc.com/haitunmarket/docs'; 64 | if (e === '交互') 65 | url = 'https://yuque.antfin-inc.com/books/share/411ef2eb-0b33-4e1c-a440-b7a2a68fecbb'; 66 | this.openUrl(url); 67 | }); 68 | 69 | this.webContents.on('handleBuildLocalSymbol', () => { 70 | const symbol = new handleBuildLocalSymbol().build(); 71 | this.sendWebview('local-symbols-data', symbol.data); 72 | this.sendWebview('local-symbols-time', symbol.time); 73 | }); 74 | } 75 | 76 | color() { 77 | this.webContents.on('handleColor', e => new handleColor().start(e)); 78 | } 79 | 80 | note() { 81 | this.webContents.on('setHeader', () => new handleNote().start('header')); 82 | this.webContents.on('setSubHeader', () => new handleNote().start('subheader')); 83 | this.webContents.on('setText', () => new handleNote().start('text')); 84 | this.webContents.on('setBlock', () => new handleNote().start('block')); 85 | this.webContents.on('setList', () => new handleNote().start('list')); 86 | this.webContents.on('setUl', () => new handleNote().start('ul')); 87 | this.webContents.on('setPoint', () => new handleNote().start('point')); 88 | this.webContents.on('setRound', () => new handleNote().start('round')); 89 | this.webContents.on('setIf', () => new handleNote().start('if')); 90 | this.webContents.on('setChangelog', () => new handleNote().start('changelog')); 91 | } 92 | 93 | line() { 94 | this.webContents.on('handleLine', () => new handleLine().start()); 95 | this.webContents.on('handleChange', () => new handleChange().start()); 96 | this.webContents.on('handleDash', () => new handleDash().start()); 97 | } 98 | 99 | layer() { 100 | this.webContents.on('handleTopLite', () => new handleFrontBackLite().start('置顶')); 101 | this.webContents.on('handleBottomLite', () => new handleFrontBackLite().start('置底')); 102 | this.webContents.on('handleTop', () => new handleFrontBack().start('置顶')); 103 | this.webContents.on('handleBottom', () => new handleFrontBack().start('置底')); 104 | this.webContents.on('handleSort', () => new handleSort().start()); 105 | this.webContents.on('handleLayout', () => new handleLayout().start()); 106 | this.webContents.on('handleHeight', () => new handleHeight().start()); 107 | this.webContents.on('handleBlender', () => new handleBlender().start()); 108 | } 109 | 110 | plate() { 111 | this.webContents.on('handleIgnore', () => new handleIgnore().start()); 112 | this.webContents.on('handleTitle', () => new handleTitle().start()); 113 | this.webContents.on('handlePlate', () => new handlePlate().start()); 114 | this.webContents.on('handleExport', () => new handleExport().start()); 115 | } 116 | 117 | word() { 118 | this.webContents.on('handleWord', e => { 119 | this.ui.success(`「${e}」已复制到剪切板`); 120 | }); 121 | } 122 | 123 | yuque() { 124 | this.webContents.on('handleYuque', () => { 125 | const url = 'https://www.yuque.com/canisminor/anto/readme'; 126 | this.openUrl(url); 127 | }); 128 | } 129 | 130 | dev() { 131 | this.webContents.on('devNumber', () => new devNumber().start()); 132 | this.webContents.on('devSymbol', () => new devSymbol().start()); 133 | this.webContents.on('devColor', () => new devColor().start()); 134 | this.webContents.on('devTest', () => new devTest().start()); 135 | } 136 | 137 | config() { 138 | this.webContents.on('handleClose', () => { 139 | this.browserWindow.destroy(); 140 | }); 141 | 142 | this.webContents.on('changeMode', e => { 143 | this.setting.set('panel-mode', e); 144 | this.ui.success(`切换到「${e}模式」`); 145 | }); 146 | 147 | this.webContents.on('closeSetting', e => { 148 | this.browserWindow.setSize(this.width, this.height, true); 149 | if (!e) return; 150 | console.log('[setting]', e); 151 | _.forEach(e, (value, key) => this.setting.set(`config-${key}`, value)); 152 | this.ui.success(`保存设置成功`); 153 | }); 154 | } 155 | 156 | run() { 157 | this.panel(); 158 | this.symbol(); 159 | this.color(); 160 | this.note(); 161 | this.line(); 162 | this.layer(); 163 | this.plate(); 164 | this.yuque(); 165 | this.word(); 166 | this.dev(); 167 | this.config(); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/sketch.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import { join } from 'path'; 3 | import sketch from 'sketch/dom'; 4 | import SketchLayer from './api/layer'; 5 | import SketchCreate from './api/create'; 6 | import sketchUI from './api/ui'; 7 | import SketchSetting from './api/setting'; 8 | import SketchLibrary from './api/library'; 9 | import SketchNative from './api/native'; 10 | 11 | export default class Sketch { 12 | constructor() { 13 | this.namespace = 'Anto'; 14 | } 15 | 16 | // Export 17 | export(...props) { 18 | sketch.export(...props); 19 | } 20 | 21 | // UI 22 | get ui() { 23 | return new sketchUI(); 24 | } 25 | 26 | get library() { 27 | return new SketchLibrary(); 28 | } 29 | 30 | get create() { 31 | return new SketchCreate(); 32 | } 33 | 34 | get layer() { 35 | return new SketchLayer(); 36 | } 37 | 38 | get setting() { 39 | return new SketchSetting(); 40 | } 41 | 42 | get fileName() { 43 | const fileName = this.native.document.displayName().stringByDeletingPathExtension(); 44 | return String(fileName); 45 | } 46 | 47 | get filePath() { 48 | const filePath = this.native.document.fileURL() 49 | ? this.native.document 50 | .fileURL() 51 | .path() 52 | .stringByDeletingLastPathComponent() 53 | : '~'; 54 | return String(filePath); 55 | } 56 | 57 | // Setting 58 | get line() { 59 | return this.setting.getDocument(this.document, 'anto-line'); 60 | } 61 | 62 | set line(e) { 63 | return this.setting.setDocument(this.document, 'anto-line', e); 64 | } 65 | 66 | getLine(aId, bId) { 67 | const data = this.line; 68 | if (!data) this.line = {}; 69 | if (!data || !data[aId] || !data[aId][bId]) return false; 70 | return this.layer.getById(this.document, data[aId][bId]); 71 | } 72 | 73 | setLine(aId, bId, lineId) { 74 | const data = this.line; 75 | if (!data) this.line = {}; 76 | if (!data[aId]) data[aId] = {}; 77 | if (!data[bId]) data[bId] = {}; 78 | data[aId][bId] = lineId; 79 | data[bId][aId] = lineId; 80 | this.line = data; 81 | console.log(this.line); 82 | } 83 | 84 | get mode() { 85 | return this.setting.get('panel-mode'); 86 | } 87 | 88 | set mode(e) { 89 | return this.setting.set('panel-mode', e); 90 | } 91 | 92 | // Document 93 | get document() { 94 | return sketch.getSelectedDocument(); 95 | } 96 | 97 | get symbols() { 98 | const symbols = []; 99 | _.forEach(this.document.pages, page => { 100 | _.forEach(page.layers, l => { 101 | if (l.type && l.type === 'SymbolMaster') { 102 | symbols.push(l); 103 | } 104 | }); 105 | }); 106 | return symbols; 107 | } 108 | 109 | get allSymbols() { 110 | return this.document.getSymbols(); 111 | } 112 | 113 | get page() { 114 | return this.document.selectedPage; 115 | } 116 | 117 | get artboards() { 118 | return _.filter(this.page.layers, l => l.type && l.type === 'Artboard'); 119 | } 120 | 121 | // selection 122 | 123 | get selection() { 124 | return this.document.selectedLayers; 125 | } 126 | 127 | selectionClear() { 128 | this.selection.clear(); 129 | } 130 | 131 | selectionSet(layers) { 132 | if (_.isArray(layers)) { 133 | _.forEach(layers, l => (l.selected = true)); 134 | } else { 135 | layers.selected = true; 136 | } 137 | } 138 | 139 | // native 140 | get context() { 141 | return NSDocumentController.sharedDocumentController(); 142 | } 143 | 144 | get native() { 145 | return new SketchNative(); 146 | } 147 | 148 | // path 149 | 150 | get pluginPath() { 151 | return this.setting.get('url'); 152 | } 153 | 154 | get pluginResourcesPath() { 155 | return join(this.pluginPath, 'Contents', 'Resources'); 156 | } 157 | 158 | // open 159 | 160 | openUrl(url) { 161 | url = NSURL.URLWithString(url); 162 | NSWorkspace.sharedWorkspace().openURL(url); 163 | } 164 | 165 | openPath(path) { 166 | path = NSURL.fileURLWithPath(path); 167 | NSWorkspace.sharedWorkspace().openURL(path); 168 | } 169 | 170 | // utils 171 | 172 | changeBasis(layer, option = {}) { 173 | layer.frame = layer.frame.changeBasis(option); 174 | } 175 | 176 | setGroup(group, layers) { 177 | _.forEach(layers, l => { 178 | l.frame = l.frame.changeBasis({ from: l.parent, to: group }); 179 | l.parent = group; 180 | }); 181 | } 182 | 183 | resizeGroup(group) { 184 | let minX = 0; 185 | let minY = 0; 186 | let maxX = 0; 187 | let maxY = 0; 188 | const Layers = group.layers; 189 | _.forEach(Layers, l => { 190 | const size = l.frame.changeBasis({ from: group, to: group.parent }); 191 | if (size.x < minX) minX = size.x; 192 | if (size.y < minY) minY = size.y; 193 | if (size.x + size.width > maxX) maxX = size.x + size.width; 194 | if (size.y + size.height > maxY) maxY = size.y + size.height; 195 | l.frame = l.frame.changeBasis({ from: group, to: this.page }); 196 | l.parent = this.page; 197 | }); 198 | group.frame.x = minX; 199 | group.frame.y = minY; 200 | group.frame.width = maxX - minX; 201 | group.frame.height = maxY - minY; 202 | _.forEach(Layers, l => { 203 | l.frame = l.frame.changeBasis({ from: this.page, to: group }); 204 | l.parent = group; 205 | }); 206 | } 207 | 208 | setById(instance, id, value) { 209 | const result = _.filter(instance.overrides, o => o.id === id); 210 | if (result.length === 0) return this.ui.message(`请检查Override-${instance.name}`); 211 | instance.setOverrideValue(result[0], value); 212 | } 213 | 214 | setByValue(instance, defaultValue, value) { 215 | const result = _.filter(instance.overrides, o => o.value === defaultValue); 216 | if (result.length === 0) return this.ui.message(`请检查Override-${instance.name}`); 217 | instance.setOverrideValue(result[0], value); 218 | } 219 | 220 | sortOrder(page = this.page) { 221 | const GroupTop = this.layer.deepGet(page, '@置顶'); 222 | const GroupLine = this.layer.deepGet(page, '@交互连线'); 223 | const GroupTitle = this.layer.deepGet(page, '@画板标题'); 224 | const GroupShadow = this.layer.deepGet(page, '@画板投影'); 225 | const GorupBottom = this.layer.deepGet(page, '@置底'); 226 | const GroupBg = this.layer.deepGet(page, '@制版'); 227 | // Front 228 | if (GroupTitle) _.forEach(GroupTitle, l => l.moveToFront()); 229 | if (GroupLine) _.forEach(GroupLine, l => l.moveToFront()); 230 | if (GroupTop) _.forEach(GroupTop, l => l.moveToFront()); 231 | // Back 232 | if (GroupShadow) _.forEach(GroupShadow, l => l.moveToBack()); 233 | if (GorupBottom) _.forEach(GorupBottom, l => l.moveToBack()); 234 | if (GroupBg) _.forEach(GroupBg, l => l.moveToBack()); 235 | } 236 | 237 | // run 238 | 239 | run() {} 240 | 241 | start(...props) { 242 | try { 243 | console.log('[Start]', this.namespace); 244 | this.run(...props); 245 | console.log('[End]', this.namespace); 246 | } catch (e) { 247 | console.log('[Error]', this.namespace, e); 248 | this.ui.alert(`🔵😥 ${this.namespace}`, String(e)); 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/update.js: -------------------------------------------------------------------------------- 1 | import compareVersions from 'compare-versions'; 2 | import UI from 'sketch/ui'; 3 | 4 | export const update = context => { 5 | fetch('https://api.github.com/repos/canisminor1990/anto/releases/latest') 6 | .then(res => res.json()) 7 | .then(json => { 8 | const { name, assets } = json; 9 | const result = compareVersions(name, String(context.plugin.version())); 10 | if (result === 1) { 11 | const url = assets[0].browser_download_url; 12 | const ok = selectPanel(`发现最新版本 🔵 Anto,是否立即更新?`, [name]); 13 | if (ok) { 14 | NSWorkspace.sharedWorkspace().openURL(NSURL.URLWithString(url)); 15 | } 16 | } 17 | }); 18 | }; 19 | 20 | function selectPanel(title, options = []) { 21 | const selection = UI.getSelectionFromUser(title, options); 22 | const ok = selection[2]; 23 | const value = options[selection[1]]; 24 | return ok ? value : false; 25 | } 26 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import UI from 'sketch/ui'; 2 | import _ from 'lodash'; 3 | 4 | // 找到唯一图层 5 | export const find = (array, key, value) => { 6 | const result = _.filter(array, m => m[key] === value); 7 | return result.length > 0 ? result[0] : false; 8 | }; 9 | 10 | // 选取指定图层 11 | export const getLayer = (page, name) => { 12 | let getLayer = false; 13 | page.layers.forEach(layer => { 14 | if (layer.name === name) getLayer = layer; 15 | }); 16 | return getLayer; 17 | }; 18 | 19 | // 删除指定图层 20 | export const removeLayer = (page, name) => { 21 | page.layers.forEach(layer => { 22 | if (layer.name === name) layer.remove(); 23 | }); 24 | }; 25 | 26 | // 全局删除指定图层 27 | export const globalRemoveLayer = (document, name) => { 28 | try { 29 | const layers = document.getLayersNamed(name); 30 | if (layers.length > 0) _.forEach(layers, layer => layer.remove()); 31 | } catch (e) { 32 | console.log('globalRemoveLayer', e); 33 | } 34 | }; 35 | 36 | // 设置Symbol的Override 37 | export const setById = (instance, id, value) => { 38 | const result = _.filter(instance.overrides, o => o.id === id); 39 | if (result.length === 0) return UI.message(`请检查Override-${instance.name}`); 40 | instance.setOverrideValue(result[0], value); 41 | }; 42 | 43 | export const setByValue = (instance, setByValue, value) => { 44 | const result = _.filter(instance.overrides, o => o.value === setByValue); 45 | if (result.length === 0) return UI.message(`请检查Override-${instance.name}`); 46 | instance.setOverrideValue(result[0], value); 47 | }; 48 | 49 | // 排序 50 | export const GroupOrder = page => { 51 | const GorupTop = find(page.layers, 'name', '@置顶'); 52 | const GroupLine = find(page.layers, 'name', '@交互连线'); 53 | const GroupTitle = find(page.layers, 'name', '@画板标题'); 54 | const GroupShadow = find(page.layers, 'name', '@画板投影'); 55 | const GorupBottom = find(page.layers, 'name', '@置底'); 56 | const GroupBg = find(page.layers, 'name', '@制版'); 57 | 58 | if (GroupTitle) GroupTitle.moveToFront(); 59 | if (GroupLine) GroupLine.moveToFront(); 60 | if (GorupTop) GorupTop.moveToFront(); 61 | 62 | if (GroupShadow) GroupShadow.moveToBack(); 63 | if (GorupBottom) GorupBottom.moveToBack(); 64 | if (GroupBg) GroupBg.moveToBack(); 65 | }; 66 | -------------------------------------------------------------------------------- /test.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "SymbolInstance", 3 | "id" : "1908A112-B2E0-43ED-A733-4F67AC56DE5A", 4 | "frame" : { 5 | "x" : 92, 6 | "y" : 115.5, 7 | "width" : 326, 8 | "height": 237 9 | }, 10 | "name" : "o", 11 | "selected" : true, 12 | "hidden" : false, 13 | "locked" : false, 14 | "exportFormats": [], 15 | "transform" : { 16 | "rotation" : 0, 17 | "flippedHorizontally": false, 18 | "flippedVertically" : false 19 | }, 20 | "style" : { 21 | "type" : "Style", 22 | "id" : "A5C51B5D-0EC2-4459-9995-736638E9604B", 23 | "opacity" : 1, 24 | "blendingMode" : "Normal", 25 | "borderOptions": { 26 | "startArrowhead": "None", 27 | "endArrowhead" : "None", 28 | "dashPattern" : [], 29 | "lineEnd" : "Butt", 30 | "lineJoin" : "Miter" 31 | }, 32 | "blur" : { 33 | "center" : { 34 | "x": 0.5, 35 | "y": 0.5 36 | }, 37 | "motionAngle": 0, 38 | "radius" : 10, 39 | "enabled" : false, 40 | "blurType" : "Gaussian" 41 | }, 42 | "fills" : [], 43 | "borders" : [], 44 | "shadows" : [], 45 | "innerShadows" : [], 46 | "styleType" : "Layer" 47 | }, 48 | "sharedStyleId": null, 49 | "symbolId" : "F7BB0B0E-EB90-43D4-AA70-9292C475B2B3", 50 | "overrides" : [ 51 | { 52 | "type" : "Override", 53 | "id" : "6F52AC17-1142-4BD9-A8D7-7B8BE37B86D4_stringValue", 54 | "path" : "6F52AC17-1142-4BD9-A8D7-7B8BE37B86D4", 55 | "property" : "stringValue", 56 | "affectedLayer" : { 57 | "type" : "Text", 58 | "id" : "6F52AC17-1142-4BD9-A8D7-7B8BE37B86D4", 59 | "frame" : { 60 | "x" : -22, 61 | "y" : 77, 62 | "width" : 371, 63 | "height": 70 64 | }, 65 | "name" : "Type something", 66 | "hidden" : false, 67 | "locked" : false, 68 | "exportFormats": [], 69 | "transform" : { 70 | "rotation" : 0, 71 | "flippedHorizontally": false, 72 | "flippedVertically" : false 73 | }, 74 | "style" : { 75 | "type" : "Style", 76 | "id" : "A4A6D38E-EB3C-4251-8216-35BEFB06611B", 77 | "opacity" : 1, 78 | "blendingMode" : "Normal", 79 | "borderOptions" : { 80 | "startArrowhead": "None", 81 | "endArrowhead" : "None", 82 | "dashPattern" : [], 83 | "lineEnd" : "Butt", 84 | "lineJoin" : "Miter" 85 | }, 86 | "blur" : { 87 | "center" : { 88 | "x": 0.5, 89 | "y": 0.5 90 | }, 91 | "motionAngle": 0, 92 | "radius" : 10, 93 | "enabled" : false, 94 | "blurType" : "Gaussian" 95 | }, 96 | "fills" : [], 97 | "borders" : [], 98 | "shadows" : [], 99 | "innerShadows" : [], 100 | "styleType" : "Text", 101 | "alignment" : "justified", 102 | "verticalAlignment": "top", 103 | "kerning" : 0, 104 | "lineHeight" : null, 105 | "paragraphSpacing" : 0, 106 | "textColor" : "#999999ff", 107 | "fontSize" : 50, 108 | "textTransform" : "none", 109 | "fontFamily" : "PingFang SC", 110 | "fontWeight" : 5 111 | }, 112 | "sharedStyleId": null, 113 | "text" : "Type something", 114 | "lineSpacing" : "constantBaseline", 115 | "fixedWidth" : false 116 | }, 117 | "symbolOverride": false, 118 | "value" : "Type something", 119 | "isDefault" : true, 120 | "editable" : true, 121 | "selected" : false 122 | } 123 | ] 124 | } --------------------------------------------------------------------------------