├── .gitignore
├── Contents
└── Resources
│ └── runner
│ ├── index.html
│ └── index.js
├── LICENSE
├── README.md
├── TODO.md
├── dist
└── blade.sketchplugin
│ └── Contents
│ ├── Resources
│ └── runner
│ │ ├── index.html
│ │ └── index.js
│ └── Sketch
│ ├── manifest.json
│ └── plugin.js
├── examples
├── testNewBlade.sketch
└── testNewBlade
│ ├── config.js
│ ├── image
│ ├── 0001_icon.png
│ ├── 0002_icon.png
│ └── 0003_Rectangle.png
│ ├── index.html
│ └── index.js
├── package-lock.json
├── package.json
├── scripts
├── attachHandler.js
├── bundle.js
└── copyManifest.js
├── src
└── plugin
│ ├── .babelrc
│ ├── .eslintignore
│ ├── .eslintrc
│ ├── CLEANUP.md
│ ├── common.js
│ ├── components
│ └── Group.js
│ ├── index.js
│ ├── parser
│ ├── A.js
│ ├── App.js
│ ├── Button.js
│ ├── Case.js
│ ├── Group.js
│ ├── Ignore.js
│ ├── Img.js
│ ├── Input.js
│ ├── Text.js
│ ├── Textarea.js
│ ├── Unknown.js
│ ├── common.js
│ ├── createParserContext.js
│ └── index.js
│ ├── utils
│ ├── core.js
│ ├── formatter.js
│ └── webview
│ │ ├── index.js
│ │ ├── panel.js
│ │ ├── webview.js
│ │ └── window.js
│ └── webpack.config.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node modules
2 | node_modules/
3 |
4 | # Build folder
5 | Contents/Sketch
6 |
7 | # Ignore knowledge base for now until cleaned up :D
8 | knowledge/
9 |
10 | # Docs files
11 | docs/.sass-cache
12 | docs/_site
13 | .idea
14 |
--------------------------------------------------------------------------------
/Contents/Resources/runner/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Blade Output
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Zhenyu Hou
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Blade
2 |
3 | [](https://github.com/sskyy/blade)
4 | [](https://gitter.im/Sketch-Plugin-Blade/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
5 |
6 | Blade is a sketch plugin designed to generate **prototype** web pages. Add simple annotation to your sketch layers and blade will get the magic done. **Click image below to see [demo video](https://vimeo.com/243630016)**.
7 |
8 | [](https://vimeo.com/243630016)
9 |
10 | ## Installation
11 |
12 | - Copy blade.sketchplugin in dist folder to sketch plugin folder.
13 | - Create a artboard to group your layers.
14 | - Run `Blade -> Export Current Artboard` on plugin menu.
15 |
16 | You can check the example file and its output in the **example** folder.
17 |
18 | ## Annotation Usage
19 |
20 | Blade use layer name annotation to convert layer to certain html component.
21 |
22 | ### A
23 |
24 | Add link to any kind of layer.
25 |
26 | ```
27 | [A?url=http://www.github.com/sskyy/blade]xxx
28 | ```
29 |
30 | ### Case
31 |
32 | Switch between different versions of any layer. Press **Command** key to see selectors.
33 |
34 | ```
35 | [Case]
36 | [#caseName=red]xxx
37 | [#caseName=blue]xxx
38 | ```
39 |
40 | ### Group
41 |
42 | Default type of group layer. Can be centered to its container.
43 |
44 | [Group?center=true]
45 |
46 | ### Ignore
47 |
48 | Layer will not show in output.
49 |
50 | ### Img
51 |
52 | Export any kind of layer as a Image.
53 |
54 | ## Roadmap
55 |
56 | ### Components
57 |
58 | - [x] Group( as default)
59 | - [x] A
60 | - [ ] Button
61 | - [x] Case
62 | - [x] Ignore
63 | - [x] Img
64 | - [ ] Input
65 | - [x] Text
66 | - [ ] Textarea
67 |
68 | ### Script support
69 |
70 | Support basic javascript
71 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 |
2 | - [x]统一打包和开发环境
3 | - [ ] Group/Text/Img/Case/A 做完就可以发一个版本
4 | - [x] 完成所需组件
5 | - [x] 完成导出脚本
6 | - [x] 支持导出图片
7 | - [x] 打包成一个完整的 blade.sketchplugin 文件
8 | - [ ] 做个完整的 demo
9 | - [ ] 完成文档
10 | - [ ] 去 boilerplate context 化
--------------------------------------------------------------------------------
/dist/blade.sketchplugin/Contents/Resources/runner/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Blade Output
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/dist/blade.sketchplugin/Contents/Sketch/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Blade",
3 | "identifier": "com.ariesate.blade",
4 | "authorEmail": "skyking_H@hotmail.com",
5 | "disableCocoaScriptPreprocessor": false,
6 | "commands": [
7 | {
8 | "script": "plugin.js",
9 | "handler": "openRunner",
10 | "name": "Open Runner",
11 | "identifier": "openRunner"
12 | },
13 | {
14 | "script": "plugin.js",
15 | "handler": "onSelectionChanged",
16 | "name": "Selection Changed",
17 | "identifier": "onSelectionChanged"
18 | },
19 | {
20 | "script": "plugin.js",
21 | "handler": "exportCurrentLayer",
22 | "name": "Export Current Artboard",
23 | "identifier": "exportCurrentLayer"
24 | },
25 | {
26 | "script": "plugin.js",
27 | "handler": "sendDataToRunner",
28 | "name": "Send Data To Runner",
29 | "identifier": "sendDataToRunner"
30 | },
31 | {
32 | "script": "plugin.js",
33 | "handlers": {
34 | "actions": {
35 | "SelectionChanged.finish": "onSelectionChanged"
36 | }
37 | }
38 | }
39 | ],
40 | "menu": {
41 | "isRoot": 1,
42 | "items": [
43 | {
44 | "title": "Blade",
45 | "items": [
46 | "exportCurrentLayer",
47 | "openRunner",
48 | "sendDataToRunner"
49 | ]
50 | }
51 | ]
52 | },
53 | "description": "A sketch plugin to generate prototype web pages.",
54 | "author": "sskyy",
55 | "version": "0.0.4"
56 | }
--------------------------------------------------------------------------------
/dist/blade.sketchplugin/Contents/Sketch/plugin.js:
--------------------------------------------------------------------------------
1 | var handlers =
2 | /******/ (function(modules) { // webpackBootstrap
3 | /******/ // The module cache
4 | /******/ var installedModules = {};
5 | /******/
6 | /******/ // The require function
7 | /******/ function __webpack_require__(moduleId) {
8 | /******/
9 | /******/ // Check if module is in cache
10 | /******/ if(installedModules[moduleId]) {
11 | /******/ return installedModules[moduleId].exports;
12 | /******/ }
13 | /******/ // Create a new module (and put it into the cache)
14 | /******/ var module = installedModules[moduleId] = {
15 | /******/ i: moduleId,
16 | /******/ l: false,
17 | /******/ exports: {}
18 | /******/ };
19 | /******/
20 | /******/ // Execute the module function
21 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
22 | /******/
23 | /******/ // Flag the module as loaded
24 | /******/ module.l = true;
25 | /******/
26 | /******/ // Return the exports of the module
27 | /******/ return module.exports;
28 | /******/ }
29 | /******/
30 | /******/
31 | /******/ // expose the modules object (__webpack_modules__)
32 | /******/ __webpack_require__.m = modules;
33 | /******/
34 | /******/ // expose the module cache
35 | /******/ __webpack_require__.c = installedModules;
36 | /******/
37 | /******/ // define getter function for harmony exports
38 | /******/ __webpack_require__.d = function(exports, name, getter) {
39 | /******/ if(!__webpack_require__.o(exports, name)) {
40 | /******/ Object.defineProperty(exports, name, {
41 | /******/ configurable: false,
42 | /******/ enumerable: true,
43 | /******/ get: getter
44 | /******/ });
45 | /******/ }
46 | /******/ };
47 | /******/
48 | /******/ // getDefaultExport function for compatibility with non-harmony modules
49 | /******/ __webpack_require__.n = function(module) {
50 | /******/ var getter = module && module.__esModule ?
51 | /******/ function getDefault() { return module['default']; } :
52 | /******/ function getModuleExports() { return module; };
53 | /******/ __webpack_require__.d(getter, 'a', getter);
54 | /******/ return getter;
55 | /******/ };
56 | /******/
57 | /******/ // Object.prototype.hasOwnProperty.call
58 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
59 | /******/
60 | /******/ // __webpack_public_path__
61 | /******/ __webpack_require__.p = "";
62 | /******/
63 | /******/ // Load entry module and return exports
64 | /******/ return __webpack_require__(__webpack_require__.s = 7);
65 | /******/ })
66 | /************************************************************************/
67 | /******/ ([
68 | /* 0 */
69 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
70 |
71 | "use strict";
72 | /* harmony export (immutable) */ __webpack_exports__["f"] = toCSSRGBA;
73 | /* harmony export (immutable) */ __webpack_exports__["b"] = extractEffectStyle;
74 | /* unused harmony export extractBoxStyle */
75 | /* harmony export (immutable) */ __webpack_exports__["c"] = extractPositionStyle;
76 | /* harmony export (immutable) */ __webpack_exports__["a"] = extractBoxRelatedStyle;
77 | /* unused harmony export extractStyle */
78 | /* harmony export (immutable) */ __webpack_exports__["e"] = layerToBase64;
79 | /* harmony export (immutable) */ __webpack_exports__["d"] = iteratorToArray;
80 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
81 |
82 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
83 |
84 | function map(arr, handler) {
85 | var result = [];
86 | for (var i = 0; i < arr.count(); i++) {
87 | result.push(handler(arr[i]));
88 | }
89 | return result;
90 | }
91 |
92 | function toCSSRGBA(RGBAStr) {
93 | return 'rgba(' + String(RGBAStr).replace(/[\(\)]/g, '').split(' ').map(function (v) {
94 | var _v$split = v.split(':'),
95 | _v$split2 = _slicedToArray(_v$split, 2),
96 | type = _v$split2[0],
97 | value = _v$split2[1];
98 |
99 | if (type !== 'a') {
100 | return Math.round(Number(value) * 256);
101 | }
102 | return Number(value);
103 | }).join(',') + ')';
104 | }
105 |
106 | function makeShadowCSS(shadow, inset) {
107 | return '' + (inset ? 'inset ' : '') + shadow.offsetX() + 'px ' + shadow.offsetY() + 'px ' + shadow.blurRadius() + 'px ' + shadow.spread() + 'px ' + toCSSRGBA(shadow.color());
108 | }
109 |
110 | function extractEffectStyle(layer) {
111 | var result = {};
112 | var fills = layer.sketchObject.style().fills();
113 | var borders = layer.sketchObject.style().borders();
114 | var shadows = layer.sketchObject.style().shadows();
115 | var innerShadows = layer.sketchObject.style().innerShadows();
116 | if (fills.count() > 0) {
117 | Object.assign(result, {
118 | background: map(fills, function (fill) {
119 | return toCSSRGBA(fill.color());
120 | }).join(',')
121 | });
122 | }
123 |
124 | if (borders.count() > 0) {
125 | var firstBorder = borders[0];
126 | Object.assign(result, {
127 | border: firstBorder.thickness() + 'px solid ' + toCSSRGBA(firstBorder.color())
128 | });
129 | }
130 |
131 | if (shadows.count() + innerShadows.count() > 0) {
132 | var totalShadows = map(shadows, makeShadowCSS).concat(map(innerShadows, function (s) {
133 | return makeShadowCSS(s, true);
134 | }));
135 |
136 | Object.assign(result, {
137 | boxShadow: totalShadows.join(',')
138 | });
139 | }
140 |
141 | return result;
142 | }
143 |
144 | function extractBoxStyle(layer) {
145 | return {
146 | width: layer.frame.width,
147 | height: layer.frame.height
148 | };
149 | }
150 |
151 | function extractPositionStyle(layer) {
152 | return {
153 | position: 'absolute',
154 | left: layer.sketchObject.absoluteRect().rulerX() - layer.container.sketchObject.absoluteRect().rulerX(),
155 | top: layer.sketchObject.absoluteRect().rulerY() - layer.container.sketchObject.absoluteRect().rulerY()
156 | };
157 | }
158 | function extractBoxRelatedStyle(layer) {
159 | return Object.assign(extractBoxStyle(layer), extractPositionStyle(layer));
160 | }
161 |
162 | function extractStyle(layer) {
163 | return Object.assign(extractBoxRelatedStyle(layer), extractEffectStyle(layer));
164 | }
165 |
166 | function layerToBase64(layer) {
167 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
168 |
169 | var fileFolder = NSTemporaryDirectory();
170 |
171 | var finalOptions = _extends({}, options, {
172 | 'use-id-for-name': true,
173 | scales: '3',
174 | formats: 'png',
175 | output: fileFolder
176 | });
177 |
178 | var fullPath = fileFolder + '/' + layer.id + '@' + finalOptions.scales + 'x.png';
179 |
180 | layer.export(finalOptions);
181 |
182 | var url = NSURL.fileURLWithPath(fullPath);
183 | var data = NSData.alloc().initWithContentsOfURL(url);
184 | var base64 = data.base64EncodedStringWithOptions(0);
185 |
186 | NSFileManager.defaultManager().removeItemAtURL_error(url, null);
187 | return 'data:image/png;base64,' + base64;
188 | }
189 |
190 | function iteratorToArray(iter) {
191 | var result = [];
192 | iter.iterate(function (item) {
193 | return result.push(item);
194 | });
195 | return result;
196 | }
197 |
198 | /***/ }),
199 | /* 1 */
200 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
201 |
202 | "use strict";
203 | /* harmony export (immutable) */ __webpack_exports__["b"] = initWithContext;
204 | /* unused harmony export loadFramework */
205 | /* unused harmony export context */
206 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return document; });
207 | /* unused harmony export selection */
208 | /* unused harmony export sketch */
209 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return pluginFolderPath; });
210 | var context = null;
211 | var document = null;
212 | var selection = null;
213 | var sketch = null;
214 |
215 | var pluginFolderPath = null;
216 | var frameworkFolderPath = '/Contents/Resources/frameworks/';
217 |
218 | function getPluginFolderPath() {
219 | // Get absolute folder path of plugin
220 | var split = context.scriptPath.split('/');
221 | split.splice(-3, 3);
222 | return split.join('/');
223 | }
224 |
225 | function initWithContext(ctx) {
226 | // This function needs to be called in the beginning of every entry point!
227 | // Set all env variables according to current context
228 | context = ctx;
229 | document = ctx.document || ctx.actionContext.document || MSDocument.currentDocument();
230 | selection = document ? document.selectedLayers() : null;
231 | pluginFolderPath = getPluginFolderPath();
232 |
233 | // Here you could load custom cocoa frameworks if you need to
234 | // loadFramework('FrameworkName', 'ClassName');
235 | // => would be loaded into ClassName in global namespace!
236 | }
237 |
238 | function loadFramework(frameworkName, frameworkClass) {
239 | // Only load framework if class not already available
240 | if (Mocha && NSClassFromString(frameworkClass) == null) {
241 | var frameworkDir = '' + pluginFolderPath + frameworkFolderPath;
242 | var mocha = Mocha.sharedRuntime();
243 | return mocha.loadFrameworkWithName_inDirectory(frameworkName, frameworkDir);
244 | }
245 | return false;
246 | }
247 |
248 |
249 |
250 | /***/ }),
251 | /* 2 */
252 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
253 |
254 | "use strict";
255 | /* unused harmony export BridgeMessageHandler */
256 | /* unused harmony export initBridgedWebView */
257 | /* unused harmony export getFilePath */
258 | /* harmony export (immutable) */ __webpack_exports__["a"] = createWebView;
259 | /* harmony export (immutable) */ __webpack_exports__["b"] = sendAction;
260 | /* unused harmony export receiveAction */
261 | /* unused harmony export windowIdentifier */
262 | /* unused harmony export panelIdentifier */
263 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__core__ = __webpack_require__(1);
264 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_cocoascript_class__ = __webpack_require__(8);
265 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_cocoascript_class___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_cocoascript_class__);
266 |
267 |
268 |
269 |
270 | // These are just used to identify the window(s)
271 | // Change them to whatever you need e.g. if you need to support multiple
272 | // windows at the same time...
273 | var windowIdentifier = 'sketch-plugin-boilerplate--window';
274 | var panelIdentifier = 'sketch-plugin-boilerplate--panel';
275 |
276 | // Since we now create the delegate in js, we need the enviroment
277 | // to stick around for as long as we need a reference to that delegate
278 | coscript.setShouldKeepAround(true);
279 |
280 | // This is a helper delegate, that handles incoming bridge messages
281 | var BridgeMessageHandler = new __WEBPACK_IMPORTED_MODULE_1_cocoascript_class___default.a({
282 | 'userContentController:didReceiveScriptMessage:': function userContentControllerDidReceiveScriptMessage(controller, message) {
283 | try {
284 | var bridgeMessage = JSON.parse(String(message.body()));
285 | receiveAction(bridgeMessage.name, bridgeMessage.data);
286 | } catch (e) {
287 | log('Could not parse bridge message');
288 | log(e.message);
289 | }
290 | }
291 | });
292 |
293 | log('BridgeMessageHandler');
294 | log(BridgeMessageHandler);
295 | log(BridgeMessageHandler.userContentController_didReceiveScriptMessage);
296 |
297 | function initBridgedWebView(frame) {
298 | var bridgeName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'SketchBridge';
299 |
300 | var config = WKWebViewConfiguration.alloc().init();
301 | var messageHandler = BridgeMessageHandler.alloc().init();
302 | config.userContentController().addScriptMessageHandler_name(messageHandler, bridgeName);
303 | config.preferences().setValue_forKey(true, 'developerExtrasEnabled');
304 | return WKWebView.alloc().initWithFrame_configuration(frame, config);
305 | }
306 |
307 | function getFilePath(file) {
308 | return __WEBPACK_IMPORTED_MODULE_0__core__["c" /* pluginFolderPath */] + '/Contents/Resources/webview/' + file;
309 | }
310 |
311 | function createWebView(path, frame) {
312 | var webView = initBridgedWebView(frame, 'Sketch');
313 | var url = path.slice(0, 4) === 'http' ? NSURL.URLWithString(path) : NSURL.fileURLWithPath(getFilePath(path));
314 | log('File URL');
315 | log(url);
316 |
317 | webView.setAutoresizingMask(NSViewWidthSizable | NSViewHeightSizable);
318 | webView.loadRequest(NSURLRequest.requestWithURL(url));
319 |
320 | return webView;
321 | }
322 |
323 | function sendAction(webView, name) {
324 | var payload = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
325 |
326 | if (!webView || !webView.evaluateJavaScript_completionHandler) {
327 | return;
328 | }
329 | // `sketchBridge` is the JS function exposed on window in the webview!
330 | var script = 'sketchBridge(' + JSON.stringify({ name: name, payload: payload }) + ');';
331 | webView.evaluateJavaScript_completionHandler(script, null);
332 | }
333 |
334 | function receiveAction(name) {
335 | var payload = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
336 |
337 | __WEBPACK_IMPORTED_MODULE_0__core__["a" /* document */].showMessage('I received a message! 😊🎉🎉');
338 | }
339 |
340 |
341 |
342 | /***/ }),
343 | /* 3 */
344 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
345 |
346 | "use strict";
347 | /* harmony export (immutable) */ __webpack_exports__["i"] = parseNameAndQuery;
348 | /* harmony export (immutable) */ __webpack_exports__["m"] = sendCommandToPanel;
349 | /* harmony export (immutable) */ __webpack_exports__["n"] = sendCommandToWindow;
350 | /* harmony export (immutable) */ __webpack_exports__["p"] = showWindow;
351 | /* harmony export (immutable) */ __webpack_exports__["o"] = showPanel;
352 | /* harmony export (immutable) */ __webpack_exports__["f"] = hidePanel;
353 | /* harmony export (immutable) */ __webpack_exports__["k"] = recursiveParse;
354 | /* harmony export (immutable) */ __webpack_exports__["h"] = isWindowOpened;
355 | /* harmony export (immutable) */ __webpack_exports__["b"] = createFolder;
356 | /* harmony export (immutable) */ __webpack_exports__["e"] = getPluginFolderPath;
357 | /* harmony export (immutable) */ __webpack_exports__["d"] = getCurrentFilePath;
358 | /* harmony export (immutable) */ __webpack_exports__["g"] = isFileExist;
359 | /* harmony export (immutable) */ __webpack_exports__["a"] = copyFile;
360 | /* harmony export (immutable) */ __webpack_exports__["q"] = writeToFile;
361 | /* harmony export (immutable) */ __webpack_exports__["l"] = removeFile;
362 | /* harmony export (immutable) */ __webpack_exports__["c"] = exportLayer;
363 | /* harmony export (immutable) */ __webpack_exports__["j"] = parseRawName;
364 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__utils_webview__ = __webpack_require__(4);
365 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
366 |
367 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
368 |
369 |
370 |
371 |
372 | function parseNameAndQuery(inputName) {
373 | var getDefaultValues = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {
374 | return {};
375 | };
376 |
377 | var defaultValues = getDefaultValues();
378 | var result = { name: '', query: defaultValues, hash: {} };
379 | var name = String(inputName);
380 | if (name[0] !== '[') return result;
381 |
382 | // name/query/hash
383 | var state = 'none';
384 | var stack = '';
385 |
386 | function clearLastStack() {
387 | if (state === 'name') {
388 | result.name = stack;
389 | } else if (state === 'query') {
390 | stack.split('&').forEach(function (subStack) {
391 | var _subStack$split = subStack.split('='),
392 | _subStack$split2 = _slicedToArray(_subStack$split, 2),
393 | queryName = _subStack$split2[0],
394 | queryValue = _subStack$split2[1];
395 |
396 | var defaultValue = defaultValues[queryName];
397 | result.query[queryName] = typeof defaultValue === 'boolean' ? Boolean(queryValue) : typeof defaultValue === 'number' ? Number(queryValue) : queryValue;
398 | });
399 | } else {
400 | // hash
401 | stack.split('&').forEach(function (subStack) {
402 | var _subStack$split3 = subStack.split('='),
403 | _subStack$split4 = _slicedToArray(_subStack$split3, 2),
404 | queryName = _subStack$split4[0],
405 | queryValue = _subStack$split4[1];
406 |
407 | var defaultValue = defaultValues[queryName];
408 | result.hash[queryName] = typeof defaultValue === 'boolean' ? Boolean(queryValue) : typeof defaultValue === 'number' ? Number(queryValue) : queryValue;
409 | });
410 | }
411 | stack = '';
412 | }
413 |
414 | for (var i = 0; i < name.length; i++) {
415 | var c = name[i];
416 | if (c === '[' && state === 'none') {
417 | state = 'name';
418 | } else if (c === '?' && (state === 'name' || state === 'hash')) {
419 | clearLastStack();
420 | state = 'query';
421 | } else if (c === '#' && (state === 'name' || state === 'query')) {
422 | clearLastStack();
423 | state = 'hash';
424 | } else if (c === ']' && (state === 'name' || state === 'query' || state === 'hash')) {
425 | clearLastStack();
426 | state = 'end';
427 | return result;
428 | } else {
429 | stack += c;
430 | }
431 | }
432 |
433 | throw new Error('tag not closed: ' + inputName);
434 | }
435 |
436 | function sendCommandToPanel(path, command, argv) {
437 | __WEBPACK_IMPORTED_MODULE_0__utils_webview__["e" /* sendPanelAction */](path, command, argv);
438 | }
439 |
440 | function sendCommandToWindow(path, command, argv) {
441 | __WEBPACK_IMPORTED_MODULE_0__utils_webview__["f" /* sendWindowAction */](path, command, argv);
442 | }
443 |
444 | function showWindow(path) {
445 | // CAUTION use path as identifier
446 | __WEBPACK_IMPORTED_MODULE_0__utils_webview__["c" /* openWindow */](path, path);
447 | }
448 |
449 | function showPanel(path) {
450 | __WEBPACK_IMPORTED_MODULE_0__utils_webview__["g" /* showPanel */](path, path);
451 | }
452 |
453 | function hidePanel(path) {
454 | __WEBPACK_IMPORTED_MODULE_0__utils_webview__["b" /* hidePanel */](path, path);
455 | }
456 |
457 | function recursiveParse(entry, parsers) {
458 | var context = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
459 |
460 | var _parseNameAndQuery = parseNameAndQuery(entry.name),
461 | name = _parseNameAndQuery.name,
462 | _parseNameAndQuery$qu = _parseNameAndQuery.query,
463 | query = _parseNameAndQuery$qu === undefined ? {} : _parseNameAndQuery$qu,
464 | hash = _parseNameAndQuery.hash;
465 |
466 | var resolvedName = name;
467 | if (resolvedName === '') {
468 | if (entry.isArtboard) {
469 | resolvedName = 'App';
470 | } else if (entry.isGroup) {
471 | resolvedName = 'Group';
472 | } else if (entry.isText) {
473 | resolvedName = 'Text';
474 | } else {
475 | // resolvedName = 'Unknown'
476 | resolvedName = 'Img';
477 | }
478 | }
479 |
480 | if (parsers[resolvedName] === undefined) {
481 | log('unknown parser ' + resolvedName + ', entry: ' + entry.name + ', parsers: ' + Object.keys(parsers).join(','));
482 | throw new Error('unknown parser ' + resolvedName);
483 | }
484 |
485 | var _parsers$resolvedName = parsers[resolvedName](entry, context),
486 | _parsers$resolvedName2 = _slicedToArray(_parsers$resolvedName, 2),
487 | result = _parsers$resolvedName2[0],
488 | next = _parsers$resolvedName2[1];
489 |
490 | if (next && next.length !== 0) {
491 | result.children = next.map(function (child) {
492 | return recursiveParse(child, parsers, context);
493 | });
494 | }
495 | return Object.assign(result, {
496 | state: Object.assign(result.state || {}, query),
497 | props: Object.assign(result.props || {}, hash)
498 | });
499 | }
500 |
501 | function isWindowOpened(path) {
502 | return Boolean(__WEBPACK_IMPORTED_MODULE_0__utils_webview__["a" /* findWebView */](path));
503 | }
504 |
505 | function createFolder(path) {
506 | var manager = NSFileManager.defaultManager();
507 | manager.createDirectoryAtPath_withIntermediateDirectories_attributes_error(path, true, null, null);
508 | // [file_manager createDirectoryAtPath:[folders objectAtIndex:i] withIntermediateDirectories:true attributes:nil error:nil];
509 | }
510 |
511 | function getPluginFolderPath(context) {
512 | // Get absolute folder path of plugin
513 | var split = context.scriptPath.split('/');
514 | split.splice(-3, 3);
515 | return split.join('/');
516 | }
517 |
518 | function getCurrentFilePath(context) {
519 | return context.document.fileURL().path().replace(/\.sketch$/, '');
520 | }
521 |
522 | function isFileExist(source) {
523 | var manager = NSFileManager.defaultManager();
524 | return manager.fileExistsAtPath(source);
525 | }
526 |
527 | function copyFile(source, target) {
528 | var manager = NSFileManager.defaultManager();
529 | if (!manager.fileExistsAtPath(source)) throw new Error('file not exist ' + source);
530 | // [file_manager copyItemAtPath:org toPath:tar error:nil];
531 | manager.copyItemAtPath_toPath_error(source, target, null);
532 | }
533 |
534 | function writeToFile(path, content) {
535 | var resultStr = NSString.stringWithFormat('%@', content);
536 | resultStr.writeToFile_atomically(path, true);
537 | }
538 |
539 | function removeFile(path) {
540 | var manager = NSFileManager.defaultManager();
541 | // [file_manager removeItemAtPath:folder error:nil]
542 | manager.removeItemAtPath_error(path, null);
543 | }
544 |
545 | function exportLayer(layer, targetFolder, name) {
546 | var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
547 |
548 | var finalOptions = _extends({}, options, {
549 | 'use-id-for-name': true,
550 | scales: '3',
551 | formats: 'png',
552 | output: targetFolder
553 | });
554 |
555 | var tmpFullPath = targetFolder + '/' + layer.id + '@' + finalOptions.scales + 'x.' + finalOptions.formats;
556 | layer.export(finalOptions);
557 | log('generating image: ' + tmpFullPath);
558 | var manager = NSFileManager.defaultManager();
559 | var targetPath = targetFolder + '/' + name + '.' + finalOptions.formats;
560 | log('renaming image to ' + targetPath);
561 | manager.moveItemAtURL_toURL_error(NSURL.fileURLWithPath(tmpFullPath), NSURL.fileURLWithPath(targetPath), null);
562 | }
563 |
564 | function parseRawName(inputName) {
565 | var name = String(inputName);
566 | return name.replace(/^\[.+\]/, '');
567 | }
568 |
569 | /***/ }),
570 | /* 4 */
571 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
572 |
573 | "use strict";
574 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__webview__ = __webpack_require__(2);
575 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__window__ = __webpack_require__(10);
576 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__panel__ = __webpack_require__(11);
577 | /* unused harmony reexport windowIdentifier */
578 | /* unused harmony reexport panelIdentifier */
579 | /* unused harmony reexport getFilePath */
580 | /* unused harmony reexport createWebView */
581 | /* unused harmony reexport sendActionToWebView */
582 | /* unused harmony reexport receiveAction */
583 | /* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return __WEBPACK_IMPORTED_MODULE_1__window__["b"]; });
584 | /* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "f", function() { return __WEBPACK_IMPORTED_MODULE_1__window__["c"]; });
585 | /* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return __WEBPACK_IMPORTED_MODULE_1__window__["a"]; });
586 | /* unused harmony reexport togglePanel */
587 | /* unused harmony reexport openPanel */
588 | /* unused harmony reexport closePanel */
589 | /* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "g", function() { return __WEBPACK_IMPORTED_MODULE_2__panel__["c"]; });
590 | /* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return __WEBPACK_IMPORTED_MODULE_2__panel__["a"]; });
591 | /* unused harmony reexport isPanelOpen */
592 | /* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "e", function() { return __WEBPACK_IMPORTED_MODULE_2__panel__["b"]; });
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 | /***/ }),
602 | /* 5 */
603 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
604 |
605 | "use strict";
606 | /* harmony export (immutable) */ __webpack_exports__["a"] = Text;
607 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__common__ = __webpack_require__(0);
608 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
609 |
610 |
611 |
612 | function Text(layer) {
613 | var node = {
614 | type: 'Text',
615 | state: {
616 | text: String(layer.sketchObject.stringValue()),
617 | style: _extends({
618 | fontSize: layer.sketchObject.fontSize(),
619 | color: Object(__WEBPACK_IMPORTED_MODULE_0__common__["f" /* toCSSRGBA */])(layer.sketchObject.textColor())
620 | }, Object(__WEBPACK_IMPORTED_MODULE_0__common__["a" /* extractBoxRelatedStyle */])(layer), {
621 | // TODO align 翻译
622 | align: layer.sketchObject.textAlignment(),
623 | // TODO line spacing 翻译成 line height
624 | // lineHeight: layer.sketchObject.lineSpacing(),
625 | letterSpacing: layer.sketchObject.characterSpacing() || 'inherit',
626 | fontFamily: String(layer.sketchObject.fontPostscriptName())
627 | })
628 | }
629 | };
630 |
631 | return [node, []];
632 | }
633 |
634 | /***/ }),
635 | /* 6 */
636 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
637 |
638 | "use strict";
639 | /* harmony export (immutable) */ __webpack_exports__["a"] = Group;
640 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__common__ = __webpack_require__(3);
641 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__common__ = __webpack_require__(0);
642 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__components_Group__ = __webpack_require__(17);
643 |
644 |
645 |
646 |
647 | var BG_NAME = 'Bg';
648 |
649 | function Group(group, _ref) {
650 | var createImgRef = _ref.createImgRef;
651 |
652 | var next = [];
653 | var bgLayer = null;
654 | group.iterate(function (layer) {
655 | // TODO 从节点上读数据
656 | if (Object(__WEBPACK_IMPORTED_MODULE_0__common__["i" /* parseNameAndQuery */])(layer.name).name === BG_NAME) {
657 | bgLayer = layer;
658 | } else {
659 | next.push(layer);
660 | }
661 | });
662 |
663 | var _parseNameAndQuery = Object(__WEBPACK_IMPORTED_MODULE_0__common__["i" /* parseNameAndQuery */])(group.name, __WEBPACK_IMPORTED_MODULE_2__components_Group__["a" /* getDefaultState */]),
664 | query = _parseNameAndQuery.query;
665 |
666 | var node = {
667 | type: 'Group',
668 | state: Object.assign(query, { style: Object(__WEBPACK_IMPORTED_MODULE_1__common__["a" /* extractBoxRelatedStyle */])(group) })
669 | };
670 |
671 | if (bgLayer) {
672 | if (bgLayer.isImage || bgLayer.isGroup) {
673 | node.state.style.background = createImgRef(bgLayer);
674 | } else {
675 | Object.assign(node.state.style, Object(__WEBPACK_IMPORTED_MODULE_1__common__["b" /* extractEffectStyle */])(bgLayer));
676 | }
677 | }
678 |
679 | return [node, next];
680 | }
681 |
682 | /***/ }),
683 | /* 7 */
684 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
685 |
686 | "use strict";
687 | Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
688 | /* harmony export (immutable) */ __webpack_exports__["openRunner"] = openRunner;
689 | /* harmony export (immutable) */ __webpack_exports__["sendDataToRunner"] = sendDataToRunner;
690 | /* harmony export (immutable) */ __webpack_exports__["exportCurrentLayer"] = exportCurrentLayer;
691 | /* harmony export (immutable) */ __webpack_exports__["onSelectionChanged"] = onSelectionChanged;
692 | /* harmony export (immutable) */ __webpack_exports__["testSendAction"] = testSendAction;
693 | /* harmony export (immutable) */ __webpack_exports__["parseLayer"] = parseLayer;
694 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__utils_core__ = __webpack_require__(1);
695 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__utils_webview__ = __webpack_require__(4);
696 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__common__ = __webpack_require__(3);
697 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__parser__ = __webpack_require__(13);
698 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__parser_createParserContext__ = __webpack_require__(24);
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 | var RUNNER_URL = 'http://127.0.0.1:8080/runner.html';
707 | var parserContext = Object(__WEBPACK_IMPORTED_MODULE_4__parser_createParserContext__["b" /* default */])();
708 |
709 | function openRunner() {
710 | Object(__WEBPACK_IMPORTED_MODULE_2__common__["p" /* showWindow */])(RUNNER_URL);
711 | }
712 |
713 | function sendDataToRunner(context) {
714 | Object(__WEBPACK_IMPORTED_MODULE_0__utils_core__["b" /* initWithContext */])(context);
715 | if (!context.api) return __WEBPACK_IMPORTED_MODULE_0__utils_core__["a" /* document */].showMessage('error context.api!');
716 | if (!Object(__WEBPACK_IMPORTED_MODULE_2__common__["h" /* isWindowOpened */])(RUNNER_URL)) {
717 | return __WEBPACK_IMPORTED_MODULE_0__utils_core__["a" /* document */].showMessage('please open runner first!');
718 | }
719 |
720 | var firstArtboard = void 0;
721 | context.api().selectedDocument.selectedPage.iterate(function (page) {
722 | if (!firstArtboard) firstArtboard = page;
723 | });
724 |
725 | if (!firstArtboard || !firstArtboard.isArtboard) return __WEBPACK_IMPORTED_MODULE_0__utils_core__["a" /* document */].showMEssage('please select an artboard');
726 | // const resultStr = NSString.stringWithFormat('%@', JSON.stringify(recursiveParse(firstArtboard, parsers)))
727 | // resultStr.writeToFile_atomically(context.document.fileURL().path().replace(/\.sketch$/, '.json'), true)
728 | var result = Object(__WEBPACK_IMPORTED_MODULE_2__common__["k" /* recursiveParse */])(firstArtboard, __WEBPACK_IMPORTED_MODULE_3__parser__["a" /* default */], parserContext);
729 | Object(__WEBPACK_IMPORTED_MODULE_2__common__["n" /* sendCommandToWindow */])(RUNNER_URL, 'config', result);
730 | return __WEBPACK_IMPORTED_MODULE_0__utils_core__["a" /* document */].showMessage('done!');
731 | }
732 |
733 | function exportCurrentLayer(context) {
734 | Object(__WEBPACK_IMPORTED_MODULE_0__utils_core__["b" /* initWithContext */])(context);
735 | if (!context.api) return __WEBPACK_IMPORTED_MODULE_0__utils_core__["a" /* document */].showMessage('error context.api!');
736 | if (!context.document.fileURL()) return __WEBPACK_IMPORTED_MODULE_0__utils_core__["a" /* document */].showMessage('please save your file first!');
737 |
738 | var sketch = context.api();
739 |
740 | var firstArtboard = void 0;
741 | sketch.selectedDocument.selectedPage.iterate(function (page) {
742 | if (!firstArtboard) firstArtboard = page;
743 | });
744 |
745 | if (!firstArtboard || !firstArtboard.isArtboard) return __WEBPACK_IMPORTED_MODULE_0__utils_core__["a" /* document */].showMessage('please select an artboard');
746 |
747 | var exportFolder = Object(__WEBPACK_IMPORTED_MODULE_2__common__["d" /* getCurrentFilePath */])(context);
748 | if (Object(__WEBPACK_IMPORTED_MODULE_2__common__["g" /* isFileExist */])(exportFolder)) Object(__WEBPACK_IMPORTED_MODULE_2__common__["l" /* removeFile */])(exportFolder);
749 |
750 | Object(__WEBPACK_IMPORTED_MODULE_2__common__["b" /* createFolder */])(exportFolder);
751 | var runnerPath = Object(__WEBPACK_IMPORTED_MODULE_2__common__["e" /* getPluginFolderPath */])(context) + '/Contents/Resources/runner';
752 | Object(__WEBPACK_IMPORTED_MODULE_2__common__["a" /* copyFile */])(runnerPath + '/index.js', exportFolder + '/index.js');
753 | Object(__WEBPACK_IMPORTED_MODULE_2__common__["a" /* copyFile */])(runnerPath + '/index.html', exportFolder + '/index.html');
754 | // TODO add error message
755 | var result = void 0;
756 | try {
757 | result = Object(__WEBPACK_IMPORTED_MODULE_2__common__["k" /* recursiveParse */])(firstArtboard, __WEBPACK_IMPORTED_MODULE_3__parser__["a" /* default */], parserContext);
758 | } catch (e) {
759 | log('parseError: ' + e.message);
760 | return __WEBPACK_IMPORTED_MODULE_0__utils_core__["a" /* document */].showMessage('error: ' + e.message);
761 | }
762 |
763 | Object(__WEBPACK_IMPORTED_MODULE_2__common__["q" /* writeToFile */])(exportFolder + '/config.js', 'sketchBridge({ payload: ' + JSON.stringify(result) + ' })');
764 |
765 | // handle images
766 | var imageFolder = exportFolder + '/' + __WEBPACK_IMPORTED_MODULE_4__parser_createParserContext__["a" /* IMAGE_FOLDER */];
767 | Object(__WEBPACK_IMPORTED_MODULE_2__common__["b" /* createFolder */])(imageFolder);
768 | parserContext.getImgRefs(function (_ref) {
769 | var id = _ref.id,
770 | name = _ref.name,
771 | options = _ref.options;
772 |
773 | Object(__WEBPACK_IMPORTED_MODULE_2__common__["c" /* exportLayer */])(sketch.selectedDocument.layerWithID(id), imageFolder, name, options);
774 | });
775 |
776 | return __WEBPACK_IMPORTED_MODULE_0__utils_core__["a" /* document */].showMessage('done!');
777 | }
778 |
779 | function onSelectionChanged(context) {
780 | Object(__WEBPACK_IMPORTED_MODULE_0__utils_core__["b" /* initWithContext */])(context);
781 | var currentDocument = context.actionContext.document;
782 | var selectedLayers = currentDocument.selectedLayers();
783 |
784 | if (!selectedLayers.containsLayers() || selectedLayers.containsMultipleLayers()) {
785 | return Object(__WEBPACK_IMPORTED_MODULE_2__common__["f" /* hidePanel */])();
786 | }
787 |
788 | var selectedLayer = selectedLayers.firstLayer();
789 | var directiveName = Object(__WEBPACK_IMPORTED_MODULE_2__common__["i" /* parseNameAndQuery */])(selectedLayer.name()).name;
790 |
791 | if (directiveName == null) {
792 | return Object(__WEBPACK_IMPORTED_MODULE_2__common__["f" /* hidePanel */])();
793 | }
794 |
795 | var command = context.command;
796 | var lastProps = command.valueForKey_onLayer('props', selectedLayer);
797 | var finalProps = lastProps == null ? {} : lastProps;
798 |
799 | Object(__WEBPACK_IMPORTED_MODULE_2__common__["o" /* showPanel */])();
800 | Object(__WEBPACK_IMPORTED_MODULE_2__common__["m" /* sendCommandToPanel */])('showProps', finalProps);
801 | }
802 |
803 | // for debug
804 | function testSendAction(context) {
805 | Object(__WEBPACK_IMPORTED_MODULE_0__utils_core__["b" /* initWithContext */])(context);
806 | __WEBPACK_IMPORTED_MODULE_1__utils_webview__["sendAction"]('aaa', { value: true });
807 | }
808 |
809 | function parseLayer(context) {
810 | Object(__WEBPACK_IMPORTED_MODULE_0__utils_core__["b" /* initWithContext */])(context);
811 | var first = void 0;
812 | context.api().selectedDocument.selectedLayers.iterate(function (layer) {
813 | if (!first) first = layer;
814 | });
815 | var result = NSString.stringWithFormat('%@', JSON.stringify(__WEBPACK_IMPORTED_MODULE_3__parser__["a" /* default */].Group(first)));
816 |
817 | result.writeToFile_atomically(context.document.fileURL().path().replace(/\.sketch$/, '.json'), true);
818 | __WEBPACK_IMPORTED_MODULE_0__utils_core__["a" /* document */].showMessage('done!');
819 | }
820 |
821 | /***/ }),
822 | /* 8 */
823 | /***/ (function(module, exports, __webpack_require__) {
824 |
825 | "use strict";
826 |
827 |
828 | Object.defineProperty(exports, "__esModule", {
829 | value: true
830 | });
831 | exports.SuperCall = undefined;
832 | exports.default = ObjCClass;
833 |
834 | var _runtime = __webpack_require__(9);
835 |
836 | exports.SuperCall = _runtime.SuperCall;
837 |
838 | // super when returnType is id and args are void
839 | // id objc_msgSendSuper(struct objc_super *super, SEL op, void)
840 |
841 | const SuperInit = (0, _runtime.SuperCall)(NSStringFromSelector("init"), [], { type: "@" });
842 |
843 | // Returns a real ObjC class. No need to use new.
844 | function ObjCClass(defn) {
845 | const superclass = defn.superclass || NSObject;
846 | const className = (defn.className || defn.classname || "ObjCClass") + NSUUID.UUID().UUIDString();
847 | const reserved = new Set(['className', 'classname', 'superclass']);
848 | var cls = MOClassDescription.allocateDescriptionForClassWithName_superclass_(className, superclass);
849 | // Add each handler to the class description
850 | const ivars = [];
851 | for (var key in defn) {
852 | const v = defn[key];
853 | if (typeof v == 'function' && key !== 'init') {
854 | var selector = NSSelectorFromString(key);
855 | cls.addInstanceMethodWithSelector_function_(selector, v);
856 | } else if (!reserved.has(key)) {
857 | ivars.push(key);
858 | cls.addInstanceVariableWithName_typeEncoding(key, "@");
859 | }
860 | }
861 |
862 | cls.addInstanceMethodWithSelector_function_(NSSelectorFromString('init'), function () {
863 | const self = SuperInit.call(this);
864 | ivars.map(name => {
865 | Object.defineProperty(self, name, {
866 | get() {
867 | return getIvar(self, name);
868 | },
869 | set(v) {
870 | (0, _runtime.object_setInstanceVariable)(self, name, v);
871 | }
872 | });
873 | self[name] = defn[name];
874 | });
875 | // If there is a passsed-in init funciton, call it now.
876 | if (typeof defn.init == 'function') defn.init.call(this);
877 | return self;
878 | });
879 |
880 | return cls.registerClass();
881 | };
882 |
883 | function getIvar(obj, name) {
884 | const retPtr = MOPointer.new();
885 | (0, _runtime.object_getInstanceVariable)(obj, name, retPtr);
886 | return retPtr.value().retain().autorelease();
887 | }
888 |
889 | /***/ }),
890 | /* 9 */
891 | /***/ (function(module, exports, __webpack_require__) {
892 |
893 | "use strict";
894 |
895 |
896 | Object.defineProperty(exports, "__esModule", {
897 | value: true
898 | });
899 | exports.SuperCall = SuperCall;
900 | exports.CFunc = CFunc;
901 | const objc_super_typeEncoding = '{objc_super="receiver"@"super_class"#}';
902 |
903 | // You can store this to call your function. this must be bound to the current instance.
904 | function SuperCall(selector, argTypes, returnType) {
905 | const func = CFunc("objc_msgSendSuper", [{ type: '^' + objc_super_typeEncoding }, { type: ":" }, ...argTypes], returnType);
906 | return function (...args) {
907 | const struct = make_objc_super(this, this.superclass());
908 | const structPtr = MOPointer.alloc().initWithValue_(struct);
909 | return func(structPtr, selector, ...args);
910 | };
911 | }
912 |
913 | // Recursively create a MOStruct
914 | function makeStruct(def) {
915 | if (typeof def !== 'object' || Object.keys(def).length == 0) {
916 | return def;
917 | }
918 | const name = Object.keys(def)[0];
919 | const values = def[name];
920 |
921 | const structure = MOStruct.structureWithName_memberNames_runtime(name, Object.keys(values), Mocha.sharedRuntime());
922 |
923 | Object.keys(values).map(member => {
924 | structure[member] = makeStruct(values[member]);
925 | });
926 |
927 | return structure;
928 | }
929 |
930 | function make_objc_super(self, cls) {
931 | return makeStruct({
932 | objc_super: {
933 | receiver: self,
934 | super_class: cls
935 | }
936 | });
937 | }
938 |
939 | // Due to particularities of the JS bridge, we can't call into MOBridgeSupport objects directly
940 | // But, we can ask key value coding to do the dirty work for us ;)
941 | function setKeys(o, d) {
942 | const funcDict = NSMutableDictionary.dictionary();
943 | funcDict.o = o;
944 | Object.keys(d).map(k => funcDict.setValue_forKeyPath(d[k], "o." + k));
945 | }
946 |
947 | // Use any C function, not just ones with BridgeSupport
948 | function CFunc(name, args, retVal) {
949 | function makeArgument(a) {
950 | if (!a) return null;
951 | const arg = MOBridgeSupportArgument.alloc().init();
952 | setKeys(arg, {
953 | type64: a.type
954 | });
955 | return arg;
956 | }
957 | const func = MOBridgeSupportFunction.alloc().init();
958 | setKeys(func, {
959 | name: name,
960 | arguments: args.map(makeArgument),
961 | returnValue: makeArgument(retVal)
962 | });
963 | return func;
964 | }
965 |
966 | /*
967 | @encode(char*) = "*"
968 | @encode(id) = "@"
969 | @encode(Class) = "#"
970 | @encode(void*) = "^v"
971 | @encode(CGRect) = "{CGRect={CGPoint=dd}{CGSize=dd}}"
972 | @encode(SEL) = ":"
973 | */
974 |
975 | function addStructToBridgeSupport(key, structDef) {
976 | // OK, so this is probably the nastiest hack in this file.
977 | // We go modify MOBridgeSupportController behind its back and use kvc to add our own definition
978 | // There isn't another API for this though. So the only other way would be to make a real bridgesupport file.
979 | const symbols = MOBridgeSupportController.sharedController().valueForKey('symbols');
980 | if (!symbols) throw Error("Something has changed within bridge support so we can't add our definitions");
981 | // If someone already added this definition, don't re-register it.
982 | if (symbols[key] !== null) return;
983 | const def = MOBridgeSupportStruct.alloc().init();
984 | setKeys(def, {
985 | name: key,
986 | type: structDef.type
987 | });
988 | symbols[key] = def;
989 | };
990 |
991 | // This assumes the ivar is an object type. Return value is pretty useless.
992 | const object_getInstanceVariable = exports.object_getInstanceVariable = CFunc("object_getInstanceVariable", [{ type: "@" }, { type: '*' }, { type: "^@" }], { type: "^{objc_ivar=}" });
993 | // Again, ivar is of object type
994 | const object_setInstanceVariable = exports.object_setInstanceVariable = CFunc("object_setInstanceVariable", [{ type: "@" }, { type: '*' }, { type: "@" }], { type: "^{objc_ivar=}" });
995 |
996 | // We need Mocha to understand what an objc_super is so we can use it as a function argument
997 | addStructToBridgeSupport('objc_super', { type: objc_super_typeEncoding });
998 |
999 | /***/ }),
1000 | /* 10 */
1001 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1002 |
1003 | "use strict";
1004 | /* harmony export (immutable) */ __webpack_exports__["b"] = open;
1005 | /* harmony export (immutable) */ __webpack_exports__["a"] = findWebView;
1006 | /* harmony export (immutable) */ __webpack_exports__["c"] = sendAction;
1007 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__webview__ = __webpack_require__(2);
1008 |
1009 |
1010 | function open(identifier) {
1011 | var path = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'index.html';
1012 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1013 |
1014 | // Sensible defaults for options
1015 | var _options$width = options.width,
1016 | width = _options$width === undefined ? 800 : _options$width,
1017 | _options$height = options.height,
1018 | height = _options$height === undefined ? 350 : _options$height,
1019 | _options$title = options.title,
1020 | title = _options$title === undefined ? 'Sketch Plugin Boilerplate' : _options$title;
1021 |
1022 |
1023 | var frame = NSMakeRect(0, 0, width, height);
1024 | var masks = NSTitledWindowMask | NSWindowStyleMaskClosable | NSResizableWindowMask;
1025 | var window = NSPanel.alloc().initWithContentRect_styleMask_backing_defer(frame, masks, NSBackingStoreBuffered, false);
1026 | window.setMinSize({ width: 200, height: 200 });
1027 |
1028 | // We use this dictionary to have a persistant storage of our NSWindow/NSPanel instance
1029 | // Otherwise the instance is stored nowhere and gets release => Window closes
1030 | var threadDictionary = NSThread.mainThread().threadDictionary();
1031 | threadDictionary[identifier] = window;
1032 |
1033 | var webView = Object(__WEBPACK_IMPORTED_MODULE_0__webview__["a" /* createWebView */])(path, frame);
1034 |
1035 | window.title = title;
1036 | window.center();
1037 | window.contentView().addSubview(webView);
1038 |
1039 | window.makeKeyAndOrderFront(null);
1040 | }
1041 |
1042 | function findWebView(identifier) {
1043 | var threadDictionary = NSThread.mainThread().threadDictionary();
1044 | var window = threadDictionary[identifier];
1045 | return window && window.contentView().subviews()[0];
1046 | }
1047 |
1048 | function sendAction(identifier, name) {
1049 | var payload = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1050 |
1051 | return Object(__WEBPACK_IMPORTED_MODULE_0__webview__["b" /* sendAction */])(findWebView(identifier), name, payload);
1052 | }
1053 |
1054 | /***/ }),
1055 | /* 11 */
1056 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1057 |
1058 | "use strict";
1059 | /* unused harmony export toggle */
1060 | /* unused harmony export open */
1061 | /* harmony export (immutable) */ __webpack_exports__["c"] = show;
1062 | /* unused harmony export close */
1063 | /* unused harmony export isOpen */
1064 | /* unused harmony export findWebView */
1065 | /* harmony export (immutable) */ __webpack_exports__["b"] = sendAction;
1066 | /* harmony export (immutable) */ __webpack_exports__["a"] = hide;
1067 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__core__ = __webpack_require__(1);
1068 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__formatter__ = __webpack_require__(12);
1069 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__webview__ = __webpack_require__(2);
1070 |
1071 |
1072 |
1073 |
1074 |
1075 | function toggle(identifier, path, width) {
1076 | if (isOpen(identifier)) {
1077 | close(identifier);
1078 | } else {
1079 | open(identifier, path, width);
1080 | }
1081 | }
1082 |
1083 | function open(identifier) {
1084 | var path = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'index.html';
1085 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1086 | var width = options.width;
1087 |
1088 | var frame = NSMakeRect(0, 0, width || 250, 600); // the height doesn't really matter here
1089 | var contentView = __WEBPACK_IMPORTED_MODULE_0__core__["a" /* document */].documentWindow().contentView();
1090 | if (!contentView || isOpen()) {
1091 | return false;
1092 | }
1093 |
1094 | var stageView = contentView.subviews().objectAtIndex(0);
1095 | var webView = Object(__WEBPACK_IMPORTED_MODULE_2__webview__["a" /* createWebView */])(path, frame);
1096 | webView.identifier = identifier;
1097 |
1098 | // Inject our webview into the right spot in the subview list
1099 | var views = stageView.subviews();
1100 | var finalViews = [];
1101 | var pushedWebView = false;
1102 | for (var i = 0; i < views.count(); i++) {
1103 | var view = views.objectAtIndex(i);
1104 | finalViews.push(view);
1105 | // NOTE: change the view identifier here if you want to add
1106 | // your panel anywhere else
1107 | if (!pushedWebView && view.identifier() == 'view_canvas') {
1108 | finalViews.push(webView);
1109 | pushedWebView = true;
1110 | }
1111 | }
1112 | // If it hasn't been pushed yet, push our web view
1113 | // E.g. when inspector is not activated etc.
1114 | if (!pushedWebView) {
1115 | finalViews.push(webView);
1116 | }
1117 | // Finally, update the subviews prop and refresh
1118 | stageView.subviews = finalViews;
1119 | stageView.adjustSubviews();
1120 | }
1121 |
1122 | function show(identifier) {
1123 | var viewToShow = findWebView(identifier);
1124 |
1125 | if (viewToShow == undefined) {
1126 | return open(identifier);
1127 | }
1128 |
1129 | var contentView = __WEBPACK_IMPORTED_MODULE_0__core__["a" /* document */].documentWindow().contentView();
1130 | if (!contentView) {
1131 | return false;
1132 | }
1133 | var stageView = contentView.subviews().objectAtIndex(0);
1134 |
1135 | viewToShow.hidden = false;
1136 | stageView.adjustSubviews();
1137 | }
1138 |
1139 | function close(identifier) {
1140 | var contentView = __WEBPACK_IMPORTED_MODULE_0__core__["a" /* document */].documentWindow().contentView();
1141 | if (!contentView) {
1142 | return false;
1143 | }
1144 |
1145 | var stageView = contentView.subviews().objectAtIndex(0);
1146 | stageView.subviews = Object(__WEBPACK_IMPORTED_MODULE_1__formatter__["a" /* toArray */])(stageView.subviews()).filter(function (view) {
1147 | return view.identifier() != identifier;
1148 | });
1149 | stageView.adjustSubviews();
1150 | }
1151 |
1152 | function isOpen(identifier) {
1153 | return !!findWebView(identifier);
1154 | }
1155 |
1156 | function findWebView(identifier) {
1157 | var contentView = __WEBPACK_IMPORTED_MODULE_0__core__["a" /* document */].documentWindow().contentView();
1158 | if (!contentView) {
1159 | return false;
1160 | }
1161 | var splitView = contentView.subviews().objectAtIndex(0);
1162 | var views = Object(__WEBPACK_IMPORTED_MODULE_1__formatter__["a" /* toArray */])(splitView.subviews());
1163 | return views.find(function (view) {
1164 | return view.identifier() == identifier;
1165 | });
1166 | }
1167 |
1168 | function sendAction(identifier, name) {
1169 | var payload = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1170 |
1171 | return Object(__WEBPACK_IMPORTED_MODULE_2__webview__["b" /* sendAction */])(findWebView(identifier), name, payload);
1172 | }
1173 |
1174 | function hide(identifier) {
1175 | var contentView = __WEBPACK_IMPORTED_MODULE_0__core__["a" /* document */].documentWindow().contentView();
1176 | if (!contentView) {
1177 | return false;
1178 | }
1179 | // Search for web view panel
1180 | var stageView = contentView.subviews().objectAtIndex(0);
1181 | var viewToHide = findWebView(identifier);
1182 |
1183 | if (viewToHide == undefined) return;
1184 |
1185 | viewToHide.hidden = true;
1186 | stageView.adjustSubviews();
1187 | }
1188 |
1189 | /***/ }),
1190 | /* 12 */
1191 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1192 |
1193 | "use strict";
1194 | /* harmony export (immutable) */ __webpack_exports__["a"] = toArray;
1195 | function toArray(object) {
1196 | if (Array.isArray(object)) {
1197 | return object;
1198 | }
1199 | var arr = [];
1200 | for (var j = 0; j < object.count(); j++) {
1201 | arr.push(object.objectAtIndex(j));
1202 | }
1203 | return arr;
1204 | }
1205 |
1206 | /***/ }),
1207 | /* 13 */
1208 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1209 |
1210 | "use strict";
1211 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__A__ = __webpack_require__(14);
1212 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__Button__ = __webpack_require__(15);
1213 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__App__ = __webpack_require__(16);
1214 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__Case__ = __webpack_require__(18);
1215 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__Group__ = __webpack_require__(6);
1216 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__Unknown__ = __webpack_require__(19);
1217 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__Ignore__ = __webpack_require__(20);
1218 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__Img__ = __webpack_require__(21);
1219 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_8__Input__ = __webpack_require__(22);
1220 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_9__Text__ = __webpack_require__(5);
1221 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_10__Textarea__ = __webpack_require__(23);
1222 |
1223 |
1224 |
1225 |
1226 |
1227 |
1228 |
1229 |
1230 |
1231 |
1232 |
1233 |
1234 | /* harmony default export */ __webpack_exports__["a"] = ({
1235 | A: __WEBPACK_IMPORTED_MODULE_0__A__["a" /* default */],
1236 | Button: __WEBPACK_IMPORTED_MODULE_1__Button__["a" /* default */],
1237 | Case: __WEBPACK_IMPORTED_MODULE_3__Case__["a" /* default */],
1238 | Group: __WEBPACK_IMPORTED_MODULE_4__Group__["a" /* default */],
1239 | App: __WEBPACK_IMPORTED_MODULE_2__App__["a" /* default */],
1240 | Unknown: __WEBPACK_IMPORTED_MODULE_5__Unknown__["a" /* default */],
1241 | Ignore: __WEBPACK_IMPORTED_MODULE_6__Ignore__["a" /* default */],
1242 | Img: __WEBPACK_IMPORTED_MODULE_7__Img__["a" /* default */],
1243 | Input: __WEBPACK_IMPORTED_MODULE_8__Input__["a" /* default */],
1244 | Text: __WEBPACK_IMPORTED_MODULE_9__Text__["a" /* default */],
1245 | Textarea: __WEBPACK_IMPORTED_MODULE_10__Textarea__["a" /* default */]
1246 | });
1247 |
1248 | /***/ }),
1249 | /* 14 */
1250 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1251 |
1252 | "use strict";
1253 | /* harmony export (immutable) */ __webpack_exports__["a"] = A;
1254 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__common__ = __webpack_require__(0);
1255 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__Text__ = __webpack_require__(5);
1256 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
1257 |
1258 |
1259 |
1260 |
1261 | function A(layer, context) {
1262 | var node = layer.isText ? _extends({}, Object(__WEBPACK_IMPORTED_MODULE_1__Text__["a" /* default */])(layer, context)[0], { type: 'A' }) : {
1263 | type: 'A',
1264 | state: {
1265 | style: Object(__WEBPACK_IMPORTED_MODULE_0__common__["c" /* extractPositionStyle */])(layer)
1266 | }
1267 | };
1268 |
1269 | var next = layer.isText ? [] : Object(__WEBPACK_IMPORTED_MODULE_0__common__["d" /* iteratorToArray */])(layer);
1270 |
1271 | return [node, next];
1272 | }
1273 |
1274 | /***/ }),
1275 | /* 15 */
1276 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1277 |
1278 | "use strict";
1279 | /* harmony export (immutable) */ __webpack_exports__["a"] = Button;
1280 | function Button() {}
1281 |
1282 | /***/ }),
1283 | /* 16 */
1284 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1285 |
1286 | "use strict";
1287 | /* harmony export (immutable) */ __webpack_exports__["a"] = App;
1288 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__Group__ = __webpack_require__(6);
1289 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
1290 |
1291 |
1292 |
1293 | function App() {
1294 | var _Group = __WEBPACK_IMPORTED_MODULE_0__Group__["a" /* default */].apply(undefined, arguments),
1295 | _Group2 = _slicedToArray(_Group, 2),
1296 | result = _Group2[0],
1297 | next = _Group2[1];
1298 |
1299 | return [Object.assign(result, {
1300 | type: 'App'
1301 | }), next];
1302 | }
1303 |
1304 | /***/ }),
1305 | /* 17 */
1306 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1307 |
1308 | "use strict";
1309 | /* harmony export (immutable) */ __webpack_exports__["a"] = getDefaultState;
1310 | /* unused harmony export render */
1311 | // import { createElement } from 'react'
1312 |
1313 | function getDefaultState() {
1314 | return {
1315 | style: {},
1316 | center: false
1317 | };
1318 | }
1319 |
1320 | function render(_ref) {
1321 | // const style = {
1322 | // ...state.style
1323 | // }
1324 | //
1325 | // if (state.center) {
1326 | // Object.assign(style, {
1327 | // position: 'relative',
1328 | // marginLeft: 'auto',
1329 | // marginRight: 'auto'
1330 | // })
1331 | // }
1332 | // return {children}
1333 |
1334 | var state = _ref.state,
1335 | children = _ref.children;
1336 | }
1337 |
1338 | /***/ }),
1339 | /* 18 */
1340 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1341 |
1342 | "use strict";
1343 | /* harmony export (immutable) */ __webpack_exports__["a"] = Case;
1344 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__common__ = __webpack_require__(0);
1345 |
1346 |
1347 | function Case(layer) {
1348 | var node = {
1349 | type: 'Case',
1350 | state: {
1351 | style: Object(__WEBPACK_IMPORTED_MODULE_0__common__["a" /* extractBoxRelatedStyle */])(layer)
1352 | }
1353 | };
1354 | var next = [];
1355 | layer.iterate(function (sub) {
1356 | next.push(sub);
1357 | });
1358 |
1359 | return [node, next];
1360 | }
1361 |
1362 | /***/ }),
1363 | /* 19 */
1364 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1365 |
1366 | "use strict";
1367 | /* harmony export (immutable) */ __webpack_exports__["a"] = Unknown;
1368 | function Unknown(layer) {
1369 | return [{
1370 | type: 'Unknown',
1371 | state: {
1372 | originName: String(layer.name)
1373 | }
1374 | }, []];
1375 | }
1376 |
1377 | /***/ }),
1378 | /* 20 */
1379 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1380 |
1381 | "use strict";
1382 | /* harmony export (immutable) */ __webpack_exports__["a"] = Ignore;
1383 | function Ignore() {
1384 | return [{
1385 | type: 'Ignore'
1386 | }, []];
1387 | }
1388 |
1389 | /***/ }),
1390 | /* 21 */
1391 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1392 |
1393 | "use strict";
1394 | /* harmony export (immutable) */ __webpack_exports__["a"] = Img;
1395 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__common__ = __webpack_require__(0);
1396 |
1397 |
1398 | function Img(layer, _ref) {
1399 | var createImgRef = _ref.createImgRef;
1400 |
1401 | return [{
1402 | type: 'Img',
1403 | state: {
1404 | src: createImgRef(layer),
1405 | style: Object(__WEBPACK_IMPORTED_MODULE_0__common__["a" /* extractBoxRelatedStyle */])(layer)
1406 | }
1407 | }, []];
1408 | }
1409 |
1410 | /***/ }),
1411 | /* 22 */
1412 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1413 |
1414 | "use strict";
1415 | /* harmony export (immutable) */ __webpack_exports__["a"] = Input;
1416 | function Input() {}
1417 |
1418 | /***/ }),
1419 | /* 23 */
1420 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1421 |
1422 | "use strict";
1423 | /* harmony export (immutable) */ __webpack_exports__["a"] = Textarea;
1424 | function Textarea() {}
1425 |
1426 | /***/ }),
1427 | /* 24 */
1428 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
1429 |
1430 | "use strict";
1431 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return IMAGE_FOLDER; });
1432 | /* harmony export (immutable) */ __webpack_exports__["b"] = createParserContext;
1433 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__common__ = __webpack_require__(0);
1434 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__common__ = __webpack_require__(3);
1435 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
1436 |
1437 |
1438 |
1439 |
1440 | function makeResourcePath(id, name) {
1441 | var idStr = String(id);
1442 | return '' + new Array(4 - idStr.length).fill('0').join('') + idStr + '_' + name;
1443 | }
1444 |
1445 | var IMAGE_FOLDER = 'image';
1446 |
1447 | function createParserContext() {
1448 | var imageRefs = [];
1449 | var imgSrcId = 0;
1450 | var imgNameId = 0;
1451 |
1452 | function generateName() {
1453 | imgNameId += 1;
1454 | return String(imgNameId);
1455 | }
1456 |
1457 | return {
1458 | createImgRef: function createImgRef(layer) {
1459 | // TODO 生成真实的图片并且做重名检测
1460 | var options = _extends({
1461 | scales: '2',
1462 | formats: 'png'
1463 | }, Object(__WEBPACK_IMPORTED_MODULE_1__common__["i" /* parseNameAndQuery */])(layer.name).query);
1464 | if (options.formats === 'base64') return Object(__WEBPACK_IMPORTED_MODULE_0__common__["e" /* layerToBase64 */])(layer);
1465 | imgSrcId += 1;
1466 | var name = makeResourcePath(imgSrcId, Object(__WEBPACK_IMPORTED_MODULE_1__common__["j" /* parseRawName */])(layer.name) || generateName());
1467 | imageRefs.push({ id: layer.id, name: name, options: options });
1468 | log('get iamge name ' + name);
1469 | log('returing image ref: ' + IMAGE_FOLDER + '/' + name + '.' + options.formats);
1470 | return IMAGE_FOLDER + '/' + name + '.' + options.formats;
1471 | },
1472 | getImgRefs: function getImgRefs(handler) {
1473 | imageRefs.forEach(handler);
1474 | }
1475 | };
1476 | }
1477 |
1478 | /***/ })
1479 | /******/ ]);
1480 |
1481 | var openRunner = handlers.openRunner;
1482 |
1483 | var onSelectionChanged = handlers.onSelectionChanged;
1484 |
1485 | var exportCurrentLayer = handlers.exportCurrentLayer;
1486 |
1487 | var sendDataToRunner = handlers.sendDataToRunner;
1488 |
1489 | var undefined = handlers.undefined;
--------------------------------------------------------------------------------
/examples/testNewBlade.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sskyy/blade/216145a80c6dc210e8a822df4069ba95258b8207/examples/testNewBlade.sketch
--------------------------------------------------------------------------------
/examples/testNewBlade/config.js:
--------------------------------------------------------------------------------
1 | sketchBridge({ payload: {"type":"App","state":{"style":{"width":378,"height":608,"position":"absolute","left":0,"top":0},"center":false},"children":[{"type":"Text","state":{"text":"Cart","style":{"fontSize":20,"color":"rgba(0,0,0,1)","width":29,"height":23,"position":"absolute","left":59,"top":332,"align":0,"letterSpacing":"inherit","fontFamily":"KinoMT"}},"props":{}},{"type":"Case","state":{"style":{"width":271,"height":270,"position":"absolute","left":48,"top":46}},"children":[{"type":"Group","state":{"style":{"width":270,"height":269,"position":"absolute","left":0,"top":0},"center":false},"children":[{"type":"Img","state":{"src":"image/0001_icon.png","style":{"width":270,"height":269,"position":"absolute","left":0,"top":0}},"props":{}}],"props":{"caseName":"grey"}},{"type":"Group","state":{"style":{"width":271,"height":270,"position":"absolute","left":0,"top":0},"center":false},"children":[{"type":"Img","state":{"src":"image/0002_icon.png","style":{"width":271,"height":270,"position":"absolute","left":0,"top":0}},"props":{}}],"props":{"caseName":"red"}}],"props":{}},{"type":"A","state":{"text":"goto","style":{"fontSize":18,"color":"rgba(0,0,0,1)","width":36,"height":22,"position":"absolute","left":116,"top":332,"align":0,"letterSpacing":"inherit","fontFamily":"Helvetica"},"url":"baidu.com"},"props":{}},{"type":"A","state":{"style":{"position":"absolute","left":74,"top":418},"url":"taobao.com"},"children":[{"type":"Img","state":{"src":"image/0003_Rectangle.png","style":{"width":200,"height":91,"position":"absolute","left":0,"top":0}},"props":{}},{"type":"Text","state":{"text":"Url in Img","style":{"fontSize":18,"color":"rgba(0,0,0,1)","width":78,"height":22,"position":"absolute","left":51,"top":33,"align":0,"letterSpacing":"inherit","fontFamily":"Helvetica"}},"props":{}}],"props":{}}],"props":{}} })
--------------------------------------------------------------------------------
/examples/testNewBlade/image/0001_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sskyy/blade/216145a80c6dc210e8a822df4069ba95258b8207/examples/testNewBlade/image/0001_icon.png
--------------------------------------------------------------------------------
/examples/testNewBlade/image/0002_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sskyy/blade/216145a80c6dc210e8a822df4069ba95258b8207/examples/testNewBlade/image/0002_icon.png
--------------------------------------------------------------------------------
/examples/testNewBlade/image/0003_Rectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sskyy/blade/216145a80c6dc210e8a822df4069ba95258b8207/examples/testNewBlade/image/0003_Rectangle.png
--------------------------------------------------------------------------------
/examples/testNewBlade/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Blade Output
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sketch-plugin-blade",
3 | "version": "0.0.2",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "copy-dir": {
8 | "version": "0.3.0",
9 | "resolved": "http://registry.npm.taobao.org/copy-dir/download/copy-dir-0.3.0.tgz",
10 | "integrity": "sha1-3rLcL6nJKQ7UfIQVWpmabUX1o1g=",
11 | "dev": true,
12 | "requires": {
13 | "mkdir-p": "0.0.7"
14 | }
15 | },
16 | "mkdir-p": {
17 | "version": "0.0.7",
18 | "resolved": "http://registry.npm.taobao.org/mkdir-p/download/mkdir-p-0.0.7.tgz",
19 | "integrity": "sha1-JMXb4m2jqZ7xWKHu+aXC3Z3laDw=",
20 | "dev": true
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sketch-plugin-blade",
3 | "description": "A sketch plugin to generate prototype web pages.",
4 | "author": "sskyy ",
5 | "version": "0.0.4",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/sskyy/blade"
9 | },
10 | "devDependencies": {
11 | "babel-core": "^6.26.0",
12 | "babel-eslint": "^7.2.3",
13 | "babel-loader": "^7.1.2",
14 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
15 | "babel-plugin-transform-react-jsx": "^6.24.1",
16 | "babel-preset-es2015": "^6.24.1",
17 | "babel-preset-stage-0": "^6.24.1",
18 | "copy-dir": "^0.3.0",
19 | "eslint": "^4.5.0",
20 | "eslint-config-airbnb": "^15.1.0",
21 | "eslint-plugin-babel": "^4.1.2",
22 | "eslint-plugin-import": "^2.7.0",
23 | "eslint-plugin-jsx-a11y": "^6.0.2",
24 | "eslint-plugin-react": "^7.3.0",
25 | "webpack": "^3.5.5"
26 | },
27 | "scripts": {
28 | "start": "cd src/plugin && webpack --env.dev --watch && cd ../..",
29 | "build": "cd src/plugin && webpack --env.production && cd ../.. && node ./scripts/attachHandler.js && node ./scripts/copyManifest.js",
30 | "bundle": "npm run build && node ./scripts/bundle.js && node ./scripts/copyManifest.js -p"
31 | },
32 | "dependencies": {
33 | "babel-plugin-module-alias": "^1.6.0",
34 | "cocoascript-class": "^0.1.2",
35 | "commander": "^2.11.0",
36 | "rimraf": "^2.6.2"
37 | },
38 | "sketch": {
39 | "name": "Blade",
40 | "identifier": "com.ariesate.blade",
41 | "authorEmail": "skyking_H@hotmail.com",
42 | "disableCocoaScriptPreprocessor": false,
43 | "commands": [
44 | {
45 | "script": "plugin.js",
46 | "handler": "openRunner",
47 | "name": "Open Runner",
48 | "identifier": "openRunner"
49 | },
50 | {
51 | "script": "plugin.js",
52 | "handler": "onSelectionChanged",
53 | "name": "Selection Changed",
54 | "identifier": "onSelectionChanged"
55 | },
56 | {
57 | "script": "plugin.js",
58 | "handler": "exportCurrentLayer",
59 | "name": "Export Current Artboard",
60 | "identifier": "exportCurrentLayer"
61 | },
62 | {
63 | "script": "plugin.js",
64 | "handler": "sendDataToRunner",
65 | "name": "Send Data To Runner",
66 | "identifier": "sendDataToRunner"
67 | },
68 | {
69 | "script": "plugin.js",
70 | "handlers": {
71 | "actions": {
72 | "SelectionChanged.finish": "onSelectionChanged"
73 | }
74 | }
75 | }
76 | ],
77 | "menu": {
78 | "isRoot": 1,
79 | "items": [
80 | {
81 | "title": "Blade",
82 | "items": [
83 | "exportCurrentLayer",
84 | "openRunner",
85 | "sendDataToRunner"
86 | ]
87 | }
88 | ]
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/scripts/attachHandler.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const commands = require('../package.json').sketch.commands
4 | const file = path.join(__dirname, '../Contents/Sketch/plugin.js')
5 |
6 | commands.forEach(function (command) {
7 | var compiled = fs.readFileSync(file);
8 | compiled += "\n\nvar " + command.handler + " = handlers." + command.handler + ";";
9 | fs.writeFileSync(file, compiled);
10 | });
11 |
--------------------------------------------------------------------------------
/scripts/bundle.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const copyDir = require('copy-dir')
4 | const rimraf = require('rimraf')
5 |
6 | const dest = path.join(__dirname, '../dist')
7 | if (fs.existsSync(dest)) {
8 | rimraf.sync(dest)
9 | }
10 | fs.mkdir(dest)
11 | const src = path.join(__dirname, '../Contents')
12 |
13 | copyDir.sync(src, `${dest}/blade.sketchplugin/Contents`)
--------------------------------------------------------------------------------
/scripts/copyManifest.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | var program = require('commander');
4 | const pkg = require('../package.json')
5 |
6 | const manifest = pkg.sketch
7 |
8 | program.option('-p, --prod', 'production mode').parse(process.argv)
9 |
10 | const finalManifest = Object.assign({}, manifest, {
11 | name: program.prod ? manifest.name : `${manifest.name}-Dev`,
12 | identifier: program.prod? manifest.identifier : `${manifest.identifier}.dev`,
13 | description: pkg.description,
14 | author: pkg.author.replace(/\s<.+>$/, ''),
15 | authorEmail: pkg.author.replace(/^[\w\s]+<(.+)>$/, '$1'),
16 | version: pkg.version,
17 | menu: program.prod? manifest.menu : {
18 | ...manifest.menu,
19 | items: [{
20 | ...manifest.menu.items[0],
21 | title: `${manifest.menu.items[0].title}-Dev`,
22 | }]
23 | }
24 | })
25 |
26 | const dest = program.prod ?
27 | path.join(__dirname, '../dist/blade.sketchplugin/Contents/Sketch/manifest.json'):
28 | path.join(__dirname, '../Contents/Sketch/manifest.json')
29 |
30 | console.log(`writing manifest into ${dest} with mode: ${program.prod}`)
31 | fs.writeFileSync(dest, JSON.stringify(finalManifest , null, 2))
32 |
--------------------------------------------------------------------------------
/src/plugin/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015", {"modules": false}]
4 | ],
5 | "plugins": [
6 | "transform-object-rest-spread",
7 | ["transform-react-jsx", {
8 | "pragma": "createElement"
9 | }],
10 | ["module-alias", [
11 | { "src": "./utils", "expose": "utils" }
12 | ]]
13 | ]
14 | }
--------------------------------------------------------------------------------
/src/plugin/.eslintignore:
--------------------------------------------------------------------------------
1 | **/node_modules/**
2 | **/dist/**
3 | **/coverage/**
4 | **/lib/**
5 |
--------------------------------------------------------------------------------
/src/plugin/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint-config-airbnb"
4 | ],
5 | "env": {
6 | "browser": true,
7 | "node": true,
8 | "mocha": true,
9 | "jest": true,
10 | "es6": true
11 | },
12 | "parser": "babel-eslint",
13 | "parserOptions": {
14 | "ecmaVersion": 6,
15 | "ecmaFeatures": {
16 | "jsx": true,
17 | "experimentalObjectRestSpread": true
18 | }
19 | },
20 | "globals": {
21 | "coscript": false,
22 | "MSDocument": false,
23 | "MSDocumentWindow": false,
24 | "MSPage": false,
25 | "MSSymbolInstance": false,
26 | "MSSymbolMaster": false,
27 | "MSTextLayer": false,
28 | "NSAlert": false,
29 | "NSApp": false,
30 | "NSClassFromString": false,
31 | "NSColor": false,
32 | "NSData": false,
33 | "NSDocument": false,
34 | "NSDocumentController": false,
35 | "NSFileManager": false,
36 | "NSImage": false,
37 | "NSJSONSerialization": false,
38 | "NSMakeRect": false,
39 | "NSMutableData": false,
40 | "NSMutableURLRequest": false,
41 | "NSSaveOperation": false,
42 | "NSString": false,
43 | "NSTextField": false,
44 | "NSTextView": false,
45 | "NSThread": false,
46 | "NSTitledWindowMask": false,
47 | "NSURL": false,
48 | "NSURLRequest": false,
49 | "NSUTF8StringEncoding": false,
50 | "NSUserDefaults": false,
51 | "NSView": false,
52 | "NSViewHeightSizable": false,
53 | "NSViewWidthSizable": false,
54 | "NSWindow": false,
55 | "NSWorkspace": false,
56 | "WKWebView": false,
57 | "WKWebViewConfiguration": false,
58 | "Mocha": false,
59 | "log": false,
60 | "NSBackingStoreBuffered": false,
61 | "NSPanel": false,
62 | "NSResizableWindowMask": false,
63 | "NSWindowStyleMaskClosable": false,
64 | "SPBWebViewMessageHandler": false,
65 | "SPBWebViewMessageUtils": false,
66 | "NSString": false,
67 | "NSTemporaryDirectory": false
68 | },
69 | "plugins": [
70 | "react",
71 | "babel"
72 | ],
73 | "rules": {
74 | "func-names": 0,
75 | "arrow-body-style": 0,
76 | "react/sort-comp": 0,
77 | "react/prop-types": 0,
78 | "react/jsx-first-prop-new-line": 0,
79 | "react/jsx-filename-extension": 0,
80 | "react/react-in-jsx-scope": 0,
81 | "import/no-unresolved": 0,
82 | "no-param-reassign": 0,
83 | "no-return-assign": 0,
84 | "max-len": 0,
85 | "consistent-return": 0,
86 | "no-redeclare": 0,
87 | "semi": [
88 | 2,
89 | "never"
90 | ],
91 | "no-extra-semi": 2,
92 | "import/no-extraneous-dependencies": 0,
93 | "import/extensions": 0,
94 | "jsx-a11y/no-static-element-interactions": 0,
95 | "jsx-a11y/href-no-hash": 0,
96 | "react/no-find-dom-node": 0,
97 | "import/imports-first": 0,
98 | "react/no-string-refs": 0,
99 | "import/prefer-default-export": 0,
100 | "react/forbid-prop-types": 0,
101 | "no-plusplus": 0,
102 | "no-continue": 0,
103 | "no-underscore-dangle": 0,
104 | "no-template-curly-in-string": 0,
105 | "no-nested-ternary": 0,
106 | "no-useless-escape": 0,
107 | "no-unused-vars": [
108 | "error",
109 | {
110 | "varsIgnorePattern": "(createElement|JSDOM)"
111 | }
112 | ]
113 | }
114 | }
--------------------------------------------------------------------------------
/src/plugin/CLEANUP.md:
--------------------------------------------------------------------------------
1 |
2 | - [x] String 处理的地方
3 | - [ ] 错误处理
4 | - [ ] context.api 的问题
5 | - [x] controller/common 整理
6 | - [ ] webpack watch mode 即可搞定?
7 |
--------------------------------------------------------------------------------
/src/plugin/common.js:
--------------------------------------------------------------------------------
1 | import * as WebViewUtils from 'utils/webview'
2 |
3 | export function parseNameAndQuery(inputName, getDefaultValues = () => ({
4 | })) {
5 | const defaultValues = getDefaultValues()
6 | const result = { name: '', query: defaultValues, hash: {} }
7 | const name = String(inputName)
8 | if (name[0] !== '[') return result
9 |
10 | // name/query/hash
11 | let state = 'none'
12 | let stack = ''
13 |
14 | function clearLastStack() {
15 | if (state === 'name') {
16 | result.name = stack
17 | } else if (state === 'query') {
18 | stack.split('&').forEach((subStack) => {
19 | const [queryName, queryValue] = subStack.split('=')
20 |
21 | const defaultValue = defaultValues[queryName]
22 | result.query[queryName] = typeof defaultValue === 'boolean' ?
23 | Boolean(queryValue) :
24 | typeof defaultValue === 'number' ?
25 | Number(queryValue) :
26 | queryValue
27 | })
28 | } else {
29 | // hash
30 | stack.split('&').forEach((subStack) => {
31 | const [queryName, queryValue] = subStack.split('=')
32 |
33 | const defaultValue = defaultValues[queryName]
34 | result.hash[queryName] = typeof defaultValue === 'boolean' ?
35 | Boolean(queryValue) :
36 | typeof defaultValue === 'number' ?
37 | Number(queryValue) :
38 | queryValue
39 | })
40 | }
41 | stack = ''
42 | }
43 |
44 | for (let i = 0; i < name.length; i++) {
45 | const c = name[i]
46 | if (c === '[' && state === 'none') {
47 | state = 'name'
48 | } else if (c === '?' && (state === 'name' || state === 'hash')) {
49 | clearLastStack()
50 | state = 'query'
51 | } else if (c === '#' && (state === 'name' || state === 'query')) {
52 | clearLastStack()
53 | state = 'hash'
54 | } else if (c === ']' && (state === 'name' || state === 'query' || state === 'hash')) {
55 | clearLastStack()
56 | state = 'end'
57 | return result
58 | } else {
59 | stack += c
60 | }
61 | }
62 |
63 | throw new Error(`tag not closed: ${inputName}`)
64 | }
65 |
66 | export function sendCommandToPanel(path, command, argv) {
67 | WebViewUtils.sendPanelAction(path, command, argv)
68 | }
69 |
70 | export function sendCommandToWindow(path, command, argv) {
71 | WebViewUtils.sendWindowAction(path, command, argv)
72 | }
73 |
74 | export function showWindow(path) {
75 | // CAUTION use path as identifier
76 | WebViewUtils.openWindow(path, path)
77 | }
78 |
79 | export function showPanel(path) {
80 | WebViewUtils.showPanel(path, path)
81 | }
82 |
83 | export function hidePanel(path) {
84 | WebViewUtils.hidePanel(path, path)
85 | }
86 |
87 | export function recursiveParse(entry, parsers, context = {}) {
88 | const { name, query = {}, hash } = parseNameAndQuery(entry.name)
89 | let resolvedName = name
90 | if (resolvedName === '') {
91 | if (entry.isArtboard) {
92 | resolvedName = 'App'
93 | } else if (entry.isGroup) {
94 | resolvedName = 'Group'
95 | } else if (entry.isText) {
96 | resolvedName = 'Text'
97 | } else {
98 | // resolvedName = 'Unknown'
99 | resolvedName = 'Img'
100 | }
101 | }
102 |
103 | if (parsers[resolvedName] === undefined) {
104 | log(`unknown parser ${resolvedName}, entry: ${entry.name}, parsers: ${Object.keys(parsers).join(',')}`)
105 | throw new Error(`unknown parser ${resolvedName}`)
106 | }
107 |
108 | const [result, next] = parsers[resolvedName](entry, context)
109 | if (next && next.length !== 0) {
110 | result.children = next.map(child => recursiveParse(child, parsers, context))
111 | }
112 | return Object.assign(result, {
113 | state: Object.assign(result.state || {}, query),
114 | props: Object.assign(result.props || {}, hash),
115 | })
116 | }
117 |
118 | export function isWindowOpened(path) {
119 | return Boolean(WebViewUtils.findWebView(path))
120 | }
121 |
122 | export function createFolder(path) {
123 | const manager = NSFileManager.defaultManager()
124 | manager.createDirectoryAtPath_withIntermediateDirectories_attributes_error(path, true, null, null)
125 | // [file_manager createDirectoryAtPath:[folders objectAtIndex:i] withIntermediateDirectories:true attributes:nil error:nil];
126 | }
127 |
128 | export function getPluginFolderPath (context) {
129 | // Get absolute folder path of plugin
130 | let split = context.scriptPath.split('/');
131 | split.splice(-3, 3);
132 | return split.join('/');
133 | }
134 |
135 | export function getCurrentFilePath(context) {
136 | return context.document.fileURL().path().replace(/\.sketch$/, '')
137 | }
138 |
139 | export function isFileExist(source) {
140 | const manager = NSFileManager.defaultManager()
141 | return manager.fileExistsAtPath(source)
142 | }
143 |
144 | export function copyFile(source, target) {
145 | const manager = NSFileManager.defaultManager()
146 | if( !manager.fileExistsAtPath(source)) throw new Error(`file not exist ${source}`)
147 | // [file_manager copyItemAtPath:org toPath:tar error:nil];
148 | manager.copyItemAtPath_toPath_error(source, target, null)
149 | }
150 |
151 | export function writeToFile(path, content) {
152 | const resultStr = NSString.stringWithFormat('%@', content)
153 | resultStr.writeToFile_atomically(path, true)
154 | }
155 |
156 | export function removeFile(path) {
157 | const manager = NSFileManager.defaultManager()
158 | // [file_manager removeItemAtPath:folder error:nil]
159 | manager.removeItemAtPath_error(path, null)
160 | }
161 |
162 | export function exportLayer(layer, targetFolder, name, options = {}) {
163 | const finalOptions = {
164 | ...options,
165 | 'use-id-for-name': true,
166 | scales: '3',
167 | formats: 'png',
168 | output: targetFolder,
169 | }
170 |
171 | const tmpFullPath = `${targetFolder}/${layer.id}@${finalOptions.scales}x.${finalOptions.formats}`
172 | layer.export(finalOptions)
173 | log(`generating image: ${tmpFullPath}`)
174 | const manager = NSFileManager.defaultManager()
175 | const targetPath = `${targetFolder}/${name}.${finalOptions.formats}`
176 | log(`renaming image to ${targetPath}` )
177 | manager.moveItemAtURL_toURL_error(
178 | NSURL.fileURLWithPath(tmpFullPath),
179 | NSURL.fileURLWithPath(targetPath),
180 | null
181 | )
182 | }
183 |
184 |
185 | export function parseRawName(inputName) {
186 | const name = String(inputName)
187 | return name.replace(/^\[.+\]/, '')
188 | }
189 |
--------------------------------------------------------------------------------
/src/plugin/components/Group.js:
--------------------------------------------------------------------------------
1 | // import { createElement } from 'react'
2 |
3 | export function getDefaultState() {
4 | return {
5 | style: {},
6 | center: false
7 | }
8 | }
9 |
10 | export function render({state, children}) {
11 | // const style = {
12 | // ...state.style
13 | // }
14 | //
15 | // if (state.center) {
16 | // Object.assign(style, {
17 | // position: 'relative',
18 | // marginLeft: 'auto',
19 | // marginRight: 'auto'
20 | // })
21 | // }
22 | // return {children}
23 | }
24 |
--------------------------------------------------------------------------------
/src/plugin/index.js:
--------------------------------------------------------------------------------
1 | import { initWithContext, document } from 'utils/core'
2 | import * as WebViewUtils from 'utils/webview'
3 | import {
4 | parseNameAndQuery,
5 | sendCommandToWindow,
6 | sendCommandToPanel,
7 | showPanel,
8 | hidePanel,
9 | showWindow,
10 | recursiveParse,
11 | isWindowOpened,
12 | createFolder,
13 | getCurrentFilePath,
14 | getPluginFolderPath,
15 | isFileExist,
16 | copyFile,
17 | writeToFile,
18 | removeFile,
19 | exportLayer
20 | } from './common'
21 | import parsers from './parser'
22 | import createParserContext, { IMAGE_FOLDER } from './parser/createParserContext'
23 |
24 |
25 | const RUNNER_URL = 'http://127.0.0.1:8080/runner.html'
26 | const parserContext = createParserContext()
27 |
28 | export function openRunner() {
29 | showWindow(RUNNER_URL)
30 | }
31 |
32 | export function sendDataToRunner(context) {
33 | initWithContext(context)
34 | if (!context.api) return document.showMessage('error context.api!')
35 | if (!isWindowOpened(RUNNER_URL)) {
36 | return document.showMessage('please open runner first!')
37 | }
38 |
39 | let firstArtboard
40 | context.api().selectedDocument.selectedPage.iterate((page) => {
41 | if (!firstArtboard) firstArtboard = page
42 | })
43 |
44 | if (!firstArtboard || !firstArtboard.isArtboard) return document.showMEssage('please select an artboard')
45 | // const resultStr = NSString.stringWithFormat('%@', JSON.stringify(recursiveParse(firstArtboard, parsers)))
46 | // resultStr.writeToFile_atomically(context.document.fileURL().path().replace(/\.sketch$/, '.json'), true)
47 | const result = recursiveParse(firstArtboard, parsers, parserContext)
48 | sendCommandToWindow(RUNNER_URL, 'config', result)
49 | return document.showMessage('done!')
50 | }
51 |
52 | export function exportCurrentLayer(context) {
53 | initWithContext(context)
54 | if (!context.api) return document.showMessage('error context.api!')
55 | if (!context.document.fileURL()) return document.showMessage('please save your file first!')
56 |
57 | const sketch = context.api()
58 |
59 | let firstArtboard
60 | sketch.selectedDocument.selectedPage.iterate((page) => {
61 | if (!firstArtboard) firstArtboard = page
62 | })
63 |
64 | if (!firstArtboard || !firstArtboard.isArtboard) return document.showMessage('please select an artboard')
65 |
66 | const exportFolder = getCurrentFilePath(context)
67 | if (isFileExist(exportFolder)) removeFile(exportFolder)
68 |
69 | createFolder(exportFolder)
70 | const runnerPath = `${getPluginFolderPath(context)}/Contents/Resources/runner`
71 | copyFile(`${runnerPath}/index.js`, `${exportFolder}/index.js`)
72 | copyFile(`${runnerPath}/index.html`, `${exportFolder}/index.html`)
73 | // TODO add error message
74 | let result
75 | try {
76 | result = recursiveParse(firstArtboard, parsers, parserContext)
77 | } catch(e) {
78 | log(`parseError: ${e.message}`)
79 | return document.showMessage(`error: ${e.message}`)
80 | }
81 |
82 | writeToFile(`${exportFolder}/config.js`, `sketchBridge({ payload: ${JSON.stringify(result)} })`)
83 |
84 | // handle images
85 | const imageFolder = `${exportFolder}/${IMAGE_FOLDER}`
86 | createFolder(imageFolder)
87 | parserContext.getImgRefs(({id, name, options}) => {
88 | exportLayer(sketch.selectedDocument.layerWithID(id), imageFolder, name, options)
89 | })
90 |
91 | return document.showMessage('done!')
92 | }
93 |
94 | export function onSelectionChanged(context) {
95 | initWithContext(context)
96 | const currentDocument = context.actionContext.document
97 | const selectedLayers = currentDocument.selectedLayers()
98 |
99 | if (!selectedLayers.containsLayers() || selectedLayers.containsMultipleLayers()) {
100 | return hidePanel()
101 | }
102 |
103 |
104 | const selectedLayer = selectedLayers.firstLayer()
105 | const directiveName = parseNameAndQuery(selectedLayer.name()).name
106 |
107 | if (directiveName == null) {
108 | return hidePanel()
109 | }
110 |
111 | const command = context.command
112 | const lastProps = command.valueForKey_onLayer('props', selectedLayer)
113 | const finalProps = lastProps == null ? {} : lastProps
114 |
115 | showPanel()
116 | sendCommandToPanel('showProps', finalProps)
117 | }
118 |
119 | // for debug
120 | export function testSendAction(context) {
121 | initWithContext(context)
122 | WebViewUtils.sendAction('aaa', { value: true })
123 | }
124 |
125 | export function parseLayer(context) {
126 | initWithContext(context)
127 | let first
128 | context.api().selectedDocument.selectedLayers.iterate((layer) => {
129 | if (!first) first = layer
130 | })
131 | const result = NSString.stringWithFormat('%@', JSON.stringify(parsers.Group(first)))
132 |
133 | result.writeToFile_atomically(context.document.fileURL().path().replace(/\.sketch$/, '.json'), true)
134 | document.showMessage('done!')
135 | }
136 |
--------------------------------------------------------------------------------
/src/plugin/parser/A.js:
--------------------------------------------------------------------------------
1 | import { extractPositionStyle, iteratorToArray } from './common'
2 | import Text from './Text'
3 |
4 | export default function A(layer, context) {
5 | const node = layer.isText ?
6 | { ...Text(layer, context)[0], type: 'A' }:
7 | {
8 | type: 'A',
9 | state: {
10 | style: extractPositionStyle(layer)
11 | }
12 | }
13 |
14 | const next = layer.isText ? [] : iteratorToArray(layer)
15 |
16 | return [node, next]
17 | }
18 |
--------------------------------------------------------------------------------
/src/plugin/parser/App.js:
--------------------------------------------------------------------------------
1 | import Group from './Group'
2 |
3 | export default function App(...argv) {
4 | const [result, next ]= Group(...argv)
5 | return [Object.assign(result, {
6 | type: 'App',
7 | }), next]
8 | }
9 |
--------------------------------------------------------------------------------
/src/plugin/parser/Button.js:
--------------------------------------------------------------------------------
1 | export default function Button() {}
2 |
--------------------------------------------------------------------------------
/src/plugin/parser/Case.js:
--------------------------------------------------------------------------------
1 | import { extractBoxRelatedStyle } from './common'
2 |
3 | export default function Case(layer) {
4 | const node = {
5 | type: 'Case',
6 | state: {
7 | style: extractBoxRelatedStyle(layer),
8 | },
9 | }
10 | const next = []
11 | layer.iterate((sub) => {
12 | next.push(sub)
13 | })
14 |
15 | return [node, next]
16 | }
17 |
--------------------------------------------------------------------------------
/src/plugin/parser/Group.js:
--------------------------------------------------------------------------------
1 | import { parseNameAndQuery } from '../common'
2 | import { extractBoxRelatedStyle, extractEffectStyle } from './common'
3 | import { getDefaultState } from '../components/Group'
4 |
5 | const BG_NAME = 'Bg'
6 |
7 | export default function Group(group, { createImgRef }) {
8 | const next = []
9 | let bgLayer = null
10 | group.iterate((layer) => {
11 | // TODO 从节点上读数据
12 | if (parseNameAndQuery(layer.name).name === BG_NAME) {
13 | bgLayer = layer
14 | } else {
15 | next.push(layer)
16 | }
17 | })
18 |
19 | const { query } = parseNameAndQuery(group.name, getDefaultState)
20 | const node = {
21 | type: 'Group',
22 | state: Object.assign(query, { style: extractBoxRelatedStyle(group) }),
23 | }
24 |
25 | if (bgLayer) {
26 | if (bgLayer.isImage || bgLayer.isGroup) {
27 | node.state.style.background = createImgRef(bgLayer)
28 | } else {
29 | Object.assign(node.state.style, extractEffectStyle(bgLayer))
30 | }
31 | }
32 |
33 | return [node, next]
34 | }
35 |
--------------------------------------------------------------------------------
/src/plugin/parser/Ignore.js:
--------------------------------------------------------------------------------
1 | export default function Ignore() {
2 | return [{
3 | type: 'Ignore',
4 | }, []]
5 | }
6 |
--------------------------------------------------------------------------------
/src/plugin/parser/Img.js:
--------------------------------------------------------------------------------
1 | import { extractBoxRelatedStyle } from './common'
2 |
3 | export default function Img(layer, { createImgRef }) {
4 | return [{
5 | type: 'Img',
6 | state: {
7 | src: createImgRef(layer),
8 | style: extractBoxRelatedStyle(layer),
9 | },
10 | }, []]
11 | }
12 |
--------------------------------------------------------------------------------
/src/plugin/parser/Input.js:
--------------------------------------------------------------------------------
1 | export default function Input(){
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/plugin/parser/Text.js:
--------------------------------------------------------------------------------
1 | import { toCSSRGBA, extractBoxRelatedStyle } from './common'
2 |
3 | export default function Text(layer) {
4 | const node = {
5 | type: 'Text',
6 | state: {
7 | text: String(layer.sketchObject.stringValue()),
8 | style: {
9 | fontSize: layer.sketchObject.fontSize(),
10 | color: toCSSRGBA(layer.sketchObject.textColor()),
11 | ...extractBoxRelatedStyle(layer),
12 | // TODO align 翻译
13 | align: layer.sketchObject.textAlignment(),
14 | // TODO line spacing 翻译成 line height
15 | // lineHeight: layer.sketchObject.lineSpacing(),
16 | letterSpacing: layer.sketchObject.characterSpacing() || 'inherit',
17 | fontFamily: String(layer.sketchObject.fontPostscriptName()),
18 | },
19 | },
20 | }
21 |
22 | return [node, []]
23 | }
24 |
--------------------------------------------------------------------------------
/src/plugin/parser/Textarea.js:
--------------------------------------------------------------------------------
1 | export default function Textarea(){
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/plugin/parser/Unknown.js:
--------------------------------------------------------------------------------
1 | export default function Unknown(layer) {
2 | return [{
3 | type: 'Unknown',
4 | state: {
5 | originName: String(layer.name),
6 | },
7 | }, []]
8 | }
9 |
--------------------------------------------------------------------------------
/src/plugin/parser/common.js:
--------------------------------------------------------------------------------
1 | function map(arr, handler) {
2 | const result = []
3 | for (let i = 0; i < arr.count(); i++) {
4 | result.push(handler(arr[i]))
5 | }
6 | return result
7 | }
8 |
9 | export function toCSSRGBA(RGBAStr) {
10 | return `rgba(${String(RGBAStr).replace(/[\(\)]/g, '').split(' ').map((v) => {
11 | const [type, value] = v.split(':')
12 | if (type !== 'a') {
13 | return Math.round(Number(value) * 256)
14 | }
15 | return Number(value)
16 | })
17 | .join(',')})`
18 | }
19 |
20 | function makeShadowCSS(shadow, inset) {
21 | return `${inset ? 'inset ' : ''}${shadow.offsetX()}px ${shadow.offsetY()}px ${shadow.blurRadius()}px ${shadow.spread()}px ${toCSSRGBA(shadow.color())}`
22 | }
23 |
24 | export function extractEffectStyle(layer) {
25 | const result = {}
26 | const fills = layer.sketchObject.style().fills()
27 | const borders = layer.sketchObject.style().borders()
28 | const shadows = layer.sketchObject.style().shadows()
29 | const innerShadows = layer.sketchObject.style().innerShadows()
30 | if (fills.count() > 0) {
31 | Object.assign(result, {
32 | background: map(fills, (fill) => {
33 | return toCSSRGBA(fill.color())
34 | }).join(','),
35 | })
36 | }
37 |
38 | if (borders.count() > 0) {
39 | const firstBorder = borders[0]
40 | Object.assign(result, {
41 | border: `${firstBorder.thickness()}px solid ${toCSSRGBA(firstBorder.color())}`,
42 | })
43 | }
44 |
45 | if ((shadows.count() + innerShadows.count()) > 0) {
46 | const totalShadows = map(shadows, makeShadowCSS).concat(map(innerShadows, s => makeShadowCSS(s, true)))
47 |
48 | Object.assign(result, {
49 | boxShadow: totalShadows.join(','),
50 | })
51 | }
52 |
53 | return result
54 | }
55 |
56 | export function extractBoxStyle(layer) {
57 | return {
58 | width: layer.frame.width,
59 | height: layer.frame.height,
60 | }
61 | }
62 |
63 | export function extractPositionStyle(layer) {
64 | return {
65 | position: 'absolute',
66 | left: layer.sketchObject.absoluteRect().rulerX() - layer.container.sketchObject.absoluteRect().rulerX(),
67 | top: layer.sketchObject.absoluteRect().rulerY() - layer.container.sketchObject.absoluteRect().rulerY(),
68 | }
69 | }
70 | export function extractBoxRelatedStyle(layer) {
71 | return Object.assign(extractBoxStyle(layer), extractPositionStyle(layer))
72 | }
73 |
74 | export function extractStyle(layer) {
75 | return Object.assign(
76 | extractBoxRelatedStyle(layer),
77 | extractEffectStyle(layer),
78 | )
79 | }
80 |
81 | export function layerToBase64(layer, options = {}) {
82 | const fileFolder = NSTemporaryDirectory()
83 |
84 | const finalOptions = {
85 | ...options,
86 | 'use-id-for-name': true,
87 | scales: '3',
88 | formats: 'png',
89 | output: fileFolder,
90 | }
91 |
92 | const fullPath = `${fileFolder}/${layer.id}@${finalOptions.scales}x.png`
93 |
94 | layer.export(finalOptions)
95 |
96 | const url = NSURL.fileURLWithPath(fullPath)
97 | const data = NSData.alloc().initWithContentsOfURL(url)
98 | const base64 = data.base64EncodedStringWithOptions(0)
99 |
100 | NSFileManager.defaultManager().removeItemAtURL_error(url, null)
101 | return `data:image/png;base64,${base64}`
102 | }
103 |
104 | export function iteratorToArray(iter) {
105 | const result = []
106 | iter.iterate((item) => result.push(item))
107 | return result
108 | }
109 |
--------------------------------------------------------------------------------
/src/plugin/parser/createParserContext.js:
--------------------------------------------------------------------------------
1 | import { layerToBase64 } from './common'
2 | import { parseNameAndQuery, parseRawName } from '../common'
3 |
4 | function makeResourcePath(id, name) {
5 | const idStr = String(id)
6 | return `${(new Array(4-idStr.length)).fill('0').join('')}${idStr}_${name}`
7 | }
8 |
9 | export const IMAGE_FOLDER = 'image'
10 |
11 |
12 |
13 | export default function createParserContext() {
14 | const imageRefs = []
15 | let imgSrcId = 0
16 | let imgNameId = 0
17 |
18 | function generateName() {
19 | imgNameId += 1
20 | return String(imgNameId)
21 | }
22 |
23 | return {
24 | createImgRef(layer) {
25 | // TODO 生成真实的图片并且做重名检测
26 | const options = {
27 | scales: '2',
28 | formats: 'png',
29 | ...parseNameAndQuery(layer.name).query
30 | }
31 | if (options.formats === 'base64') return layerToBase64(layer)
32 | imgSrcId += 1
33 | const name = makeResourcePath(imgSrcId, parseRawName(layer.name) || generateName())
34 | imageRefs.push({ id: layer.id, name, options })
35 | log(`get iamge name ${name}`)
36 | log(`returing image ref: ${IMAGE_FOLDER}/${name}.${options.formats}`)
37 | return `${IMAGE_FOLDER}/${name}.${options.formats}`
38 | },
39 | getImgRefs(handler) {
40 | imageRefs.forEach(handler)
41 | },
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/plugin/parser/index.js:
--------------------------------------------------------------------------------
1 | import A from './A'
2 | import Button from './Button'
3 | import App from './App'
4 | import Case from './Case'
5 | import Group from './Group'
6 | import Unknown from './Unknown'
7 | import Ignore from './Ignore'
8 | import Img from './Img'
9 | import Input from './Input'
10 | import Text from './Text'
11 | import Textarea from './Textarea'
12 |
13 | export default {
14 | A,
15 | Button,
16 | Case,
17 | Group,
18 | App,
19 | Unknown,
20 | Ignore,
21 | Img,
22 | Input,
23 | Text,
24 | Textarea
25 | }
26 |
--------------------------------------------------------------------------------
/src/plugin/utils/core.js:
--------------------------------------------------------------------------------
1 | let context = null;
2 | let document = null;
3 | let selection = null;
4 | let sketch = null;
5 |
6 | let pluginFolderPath = null;
7 | let frameworkFolderPath = '/Contents/Resources/frameworks/';
8 |
9 | function getPluginFolderPath () {
10 | // Get absolute folder path of plugin
11 | let split = context.scriptPath.split('/');
12 | split.splice(-3, 3);
13 | return split.join('/');
14 | }
15 |
16 | export function initWithContext (ctx) {
17 | // This function needs to be called in the beginning of every entry point!
18 | // Set all env variables according to current context
19 | context = ctx;
20 | document = ctx.document
21 | || ctx.actionContext.document
22 | || MSDocument.currentDocument();
23 | selection = document ? document.selectedLayers() : null;
24 | pluginFolderPath = getPluginFolderPath();
25 |
26 | // Here you could load custom cocoa frameworks if you need to
27 | // loadFramework('FrameworkName', 'ClassName');
28 | // => would be loaded into ClassName in global namespace!
29 | }
30 |
31 | export function loadFramework (frameworkName, frameworkClass) {
32 | // Only load framework if class not already available
33 | if (Mocha && NSClassFromString(frameworkClass) == null) {
34 | const frameworkDir = `${pluginFolderPath}${frameworkFolderPath}`;
35 | const mocha = Mocha.sharedRuntime();
36 | return mocha.loadFrameworkWithName_inDirectory(frameworkName, frameworkDir);
37 | }
38 | return false;
39 | }
40 |
41 | export {
42 | context,
43 | document,
44 | selection,
45 | sketch,
46 | pluginFolderPath
47 | };
48 |
--------------------------------------------------------------------------------
/src/plugin/utils/formatter.js:
--------------------------------------------------------------------------------
1 | export function toArray (object) {
2 | if (Array.isArray(object)) {
3 | return object;
4 | }
5 | let arr = [];
6 | for (let j = 0; j < object.count(); j++) {
7 | arr.push(object.objectAtIndex(j));
8 | }
9 | return arr;
10 | }
11 |
--------------------------------------------------------------------------------
/src/plugin/utils/webview/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | windowIdentifier,
3 | panelIdentifier,
4 | getFilePath,
5 | createWebView,
6 | sendAction as sendActionToWebView,
7 | receiveAction
8 | } from './webview';
9 |
10 | import {
11 | open as openWindow,
12 | sendAction as sendWindowAction,
13 | findWebView
14 | } from './window';
15 |
16 | import {
17 | toggle as togglePanel,
18 | open as openPanel,
19 | close as closePanel,
20 | isOpen as isPanelOpen,
21 | show as showPanel,
22 | hide as hidePanel,
23 | sendAction as sendPanelAction
24 | } from './panel';
25 |
26 | export {
27 | windowIdentifier,
28 | panelIdentifier,
29 | getFilePath,
30 | createWebView,
31 | sendActionToWebView,
32 | receiveAction,
33 |
34 | openWindow,
35 | sendWindowAction,
36 | findWebView,
37 |
38 | togglePanel,
39 | openPanel,
40 | closePanel,
41 | showPanel,
42 | hidePanel,
43 | isPanelOpen,
44 | sendPanelAction
45 | };
46 |
--------------------------------------------------------------------------------
/src/plugin/utils/webview/panel.js:
--------------------------------------------------------------------------------
1 | import { document } from 'utils/core';
2 | import { toArray } from 'utils/formatter';
3 | import { createWebView, sendAction as sendActionToWebView } from './webview';
4 |
5 | export function toggle (identifier, path, width) {
6 | if (isOpen(identifier)) {
7 | close(identifier);
8 | } else {
9 | open(identifier, path, width);
10 | }
11 | }
12 |
13 | export function open (identifier, path = 'index.html', options = {}) {
14 | const { width } = options;
15 | const frame = NSMakeRect(0, 0, width || 250, 600); // the height doesn't really matter here
16 | const contentView = document.documentWindow().contentView();
17 | if (!contentView || isOpen()) {
18 | return false;
19 | }
20 |
21 | const stageView = contentView.subviews().objectAtIndex(0);
22 | let webView = createWebView(path, frame);
23 | webView.identifier = identifier;
24 |
25 | // Inject our webview into the right spot in the subview list
26 | const views = stageView.subviews();
27 | let finalViews = [];
28 | let pushedWebView = false;
29 | for (let i = 0; i < views.count(); i++) {
30 | const view = views.objectAtIndex(i);
31 | finalViews.push(view);
32 | // NOTE: change the view identifier here if you want to add
33 | // your panel anywhere else
34 | if (!pushedWebView && view.identifier() == 'view_canvas') {
35 | finalViews.push(webView);
36 | pushedWebView = true;
37 | }
38 | }
39 | // If it hasn't been pushed yet, push our web view
40 | // E.g. when inspector is not activated etc.
41 | if (!pushedWebView) {
42 | finalViews.push(webView);
43 | }
44 | // Finally, update the subviews prop and refresh
45 | stageView.subviews = finalViews;
46 | stageView.adjustSubviews();
47 | }
48 |
49 |
50 | export function show(identifier) {
51 | const viewToShow = findWebView(identifier)
52 |
53 | if (viewToShow == undefined) {
54 | return open(identifier)
55 | }
56 |
57 | const contentView = document.documentWindow().contentView();
58 | if (!contentView) {
59 | return false;
60 | }
61 | const stageView = contentView.subviews().objectAtIndex(0);
62 |
63 | viewToShow.hidden = false
64 | stageView.adjustSubviews();
65 | }
66 |
67 | export function close (identifier) {
68 | const contentView = document.documentWindow().contentView();
69 | if (!contentView) {
70 | return false;
71 | }
72 |
73 | const stageView = contentView.subviews().objectAtIndex(0);
74 | stageView.subviews = toArray(stageView.subviews()).filter(view => {
75 | return view.identifier() != identifier;
76 | });
77 | stageView.adjustSubviews();
78 | }
79 |
80 | export function isOpen (identifier) {
81 | return !!findWebView(identifier);
82 | }
83 |
84 | export function findWebView (identifier) {
85 | const contentView = document.documentWindow().contentView();
86 | if (!contentView) {
87 | return false;
88 | }
89 | const splitView = contentView.subviews().objectAtIndex(0);
90 | const views = toArray(splitView.subviews());
91 | return views.find(view => view.identifier() == identifier);
92 | }
93 |
94 | export function sendAction (identifier, name, payload = {}) {
95 | return sendActionToWebView(findWebView(identifier), name, payload );
96 | }
97 |
98 | export function hide(identifier) {
99 | const contentView = document.documentWindow().contentView();
100 | if (!contentView) {
101 | return false;
102 | }
103 | // Search for web view panel
104 | const stageView = contentView.subviews().objectAtIndex(0);
105 | const viewToHide = findWebView(identifier)
106 |
107 | if (viewToHide == undefined) return
108 |
109 | viewToHide.hidden = true
110 | stageView.adjustSubviews();
111 | }
112 |
--------------------------------------------------------------------------------
/src/plugin/utils/webview/webview.js:
--------------------------------------------------------------------------------
1 | import { pluginFolderPath, document, context } from 'utils/core';
2 | import ObjCClass from 'cocoascript-class';
3 |
4 | // These are just used to identify the window(s)
5 | // Change them to whatever you need e.g. if you need to support multiple
6 | // windows at the same time...
7 | let windowIdentifier = 'sketch-plugin-boilerplate--window';
8 | let panelIdentifier = 'sketch-plugin-boilerplate--panel';
9 |
10 | // Since we now create the delegate in js, we need the enviroment
11 | // to stick around for as long as we need a reference to that delegate
12 | coscript.setShouldKeepAround(true);
13 |
14 | // This is a helper delegate, that handles incoming bridge messages
15 | export const BridgeMessageHandler = new ObjCClass({
16 | 'userContentController:didReceiveScriptMessage:': function (controller, message) {
17 | try {
18 | const bridgeMessage = JSON.parse(String(message.body()));
19 | receiveAction(bridgeMessage.name, bridgeMessage.data);
20 | } catch (e) {
21 | log('Could not parse bridge message');
22 | log(e.message);
23 | }
24 | }
25 | });
26 |
27 | log('BridgeMessageHandler');
28 | log(BridgeMessageHandler);
29 | log(BridgeMessageHandler.userContentController_didReceiveScriptMessage);
30 |
31 | export function initBridgedWebView (frame, bridgeName = 'SketchBridge') {
32 | const config = WKWebViewConfiguration.alloc().init();
33 | const messageHandler = BridgeMessageHandler.alloc().init();
34 | config.userContentController().addScriptMessageHandler_name(messageHandler, bridgeName);
35 | config.preferences().setValue_forKey(true, 'developerExtrasEnabled')
36 | return WKWebView.alloc().initWithFrame_configuration(frame, config);
37 | }
38 |
39 | export function getFilePath (file) {
40 | return `${pluginFolderPath}/Contents/Resources/webview/${file}`;
41 | }
42 |
43 | export function createWebView (path, frame) {
44 | const webView = initBridgedWebView(frame, 'Sketch');
45 | const url = path.slice(0, 4) === 'http' ? NSURL.URLWithString(path) : NSURL.fileURLWithPath(getFilePath(path));
46 | log('File URL');
47 | log(url);
48 |
49 | webView.setAutoresizingMask(NSViewWidthSizable | NSViewHeightSizable);
50 | webView.loadRequest(NSURLRequest.requestWithURL(url));
51 |
52 | return webView;
53 | }
54 |
55 | export function sendAction (webView, name, payload = {}) {
56 | if (!webView || !webView.evaluateJavaScript_completionHandler) {
57 | return;
58 | }
59 | // `sketchBridge` is the JS function exposed on window in the webview!
60 | const script = `sketchBridge(${JSON.stringify({name, payload})});`;
61 | webView.evaluateJavaScript_completionHandler(script, null);
62 | }
63 |
64 | export function receiveAction (name, payload = {}) {
65 | document.showMessage('I received a message! 😊🎉🎉');
66 | }
67 |
68 | export {
69 | windowIdentifier,
70 | panelIdentifier
71 | };
72 |
--------------------------------------------------------------------------------
/src/plugin/utils/webview/window.js:
--------------------------------------------------------------------------------
1 | import {
2 | createWebView,
3 | sendAction as sendActionToWebView
4 | } from './webview';
5 |
6 | export function open (identifier, path = 'index.html', options = {}) {
7 | // Sensible defaults for options
8 | const {
9 | width = 800,
10 | height = 350,
11 | title = 'Sketch Plugin Boilerplate'
12 | } = options;
13 |
14 | const frame = NSMakeRect(0, 0, width, height);
15 | const masks = NSTitledWindowMask |
16 | NSWindowStyleMaskClosable |
17 | NSResizableWindowMask;
18 | const window = NSPanel.alloc().initWithContentRect_styleMask_backing_defer(frame, masks, NSBackingStoreBuffered, false);
19 | window.setMinSize({width: 200, height: 200});
20 |
21 | // We use this dictionary to have a persistant storage of our NSWindow/NSPanel instance
22 | // Otherwise the instance is stored nowhere and gets release => Window closes
23 | let threadDictionary = NSThread.mainThread().threadDictionary();
24 | threadDictionary[identifier] = window;
25 |
26 | const webView = createWebView(path, frame);
27 |
28 | window.title = title;
29 | window.center();
30 | window.contentView().addSubview(webView);
31 |
32 | window.makeKeyAndOrderFront(null);
33 | }
34 |
35 | export function findWebView (identifier) {
36 | let threadDictionary = NSThread.mainThread().threadDictionary();
37 | const window = threadDictionary[identifier];
38 | return window && window.contentView().subviews()[0];
39 | }
40 |
41 | export function sendAction (identifier, name, payload = {}) {
42 | return sendActionToWebView(findWebView(identifier), name, payload);
43 | }
44 |
--------------------------------------------------------------------------------
/src/plugin/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | entry: './index',
5 | output: {
6 | filename: 'plugin.js',
7 | path: path.resolve(__dirname, '../../Contents/Sketch'),
8 | library: 'handlers',
9 | libraryTarget: 'var',
10 | },
11 | module: {
12 | rules: [
13 | { test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader' },
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------