├── README.md
└── sketch_user_flows.sketchplugin
└── Contents
└── Sketch
├── exportAllInterface.js
├── exportSelectedInterface.js
├── importReqfireProject.js
└── manifest.json
/README.md:
--------------------------------------------------------------------------------
1 | # Sketch User Flows
2 |
3 |
4 |
5 |
6 | ## Contents
7 | * Overview
8 | * Installation
9 | * How it works
10 | * FAQ
11 | * Get in touch
12 |
13 | ## The quickest way to create User Flows in Sketch
14 |
15 | Communicate your designs with User Flows: auto-generate User Flows and keep your designs in sync with your Reqfire project.
16 |
17 | **Generate** User Flow maps from your Reqfire project.
18 |
19 | **Sync** your designs in Sketch with your User Flows as they develop.
20 |
21 |
22 |
23 | ## Installing
24 | You can download our plugin right here:
25 |
26 |
27 |
28 |
29 |
30 | Is this your first plugin? Here's the docs to better understand [how Sketch plugins work](https://www.sketchapp.com/docs/plugins/).
31 |
32 | ## Generate and Sync User Flows
33 |
34 |
35 |
36 | ---
37 |
38 | ## How it works:
39 |
40 | ### Step 1: Create User Flows in Reqfire
41 |
42 |
43 | ### Step 2: Use the plugin to import User Flows to Sketch
44 | In Sketch: Plugin > Sketch User Flows > Import Reqfire Project
45 |
46 |
47 |
48 | ### Step 3: Sync your designs with Reqfire
49 | In Sketch: Plugin > Sketch User Flows > Sync All/Selected symbols to Reqfire
50 |
51 |
52 |
53 |
54 | ## Get in touch
55 |
56 | Feel free to create an Issue or PR. Or drop us an email at [hello@reqfire.com](mailto:hello@reqfire.com)
57 |
--------------------------------------------------------------------------------
/sketch_user_flows.sketchplugin/Contents/Sketch/exportAllInterface.js:
--------------------------------------------------------------------------------
1 | var that = this;
2 | function __skpm_run (key, context) {
3 | that.context = context;
4 |
5 | var exports =
6 | /******/ (function(modules) { // webpackBootstrap
7 | /******/ // The module cache
8 | /******/ var installedModules = {};
9 | /******/
10 | /******/ // The require function
11 | /******/ function __webpack_require__(moduleId) {
12 | /******/
13 | /******/ // Check if module is in cache
14 | /******/ if(installedModules[moduleId]) {
15 | /******/ return installedModules[moduleId].exports;
16 | /******/ }
17 | /******/ // Create a new module (and put it into the cache)
18 | /******/ var module = installedModules[moduleId] = {
19 | /******/ i: moduleId,
20 | /******/ l: false,
21 | /******/ exports: {}
22 | /******/ };
23 | /******/
24 | /******/ // Execute the module function
25 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
26 | /******/
27 | /******/ // Flag the module as loaded
28 | /******/ module.l = true;
29 | /******/
30 | /******/ // Return the exports of the module
31 | /******/ return module.exports;
32 | /******/ }
33 | /******/
34 | /******/
35 | /******/ // expose the modules object (__webpack_modules__)
36 | /******/ __webpack_require__.m = modules;
37 | /******/
38 | /******/ // expose the module cache
39 | /******/ __webpack_require__.c = installedModules;
40 | /******/
41 | /******/ // define getter function for harmony exports
42 | /******/ __webpack_require__.d = function(exports, name, getter) {
43 | /******/ if(!__webpack_require__.o(exports, name)) {
44 | /******/ Object.defineProperty(exports, name, {
45 | /******/ configurable: false,
46 | /******/ enumerable: true,
47 | /******/ get: getter
48 | /******/ });
49 | /******/ }
50 | /******/ };
51 | /******/
52 | /******/ // getDefaultExport function for compatibility with non-harmony modules
53 | /******/ __webpack_require__.n = function(module) {
54 | /******/ var getter = module && module.__esModule ?
55 | /******/ function getDefault() { return module['default']; } :
56 | /******/ function getModuleExports() { return module; };
57 | /******/ __webpack_require__.d(getter, 'a', getter);
58 | /******/ return getter;
59 | /******/ };
60 | /******/
61 | /******/ // Object.prototype.hasOwnProperty.call
62 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
63 | /******/
64 | /******/ // __webpack_public_path__
65 | /******/ __webpack_require__.p = "";
66 | /******/
67 | /******/ // Load entry module and return exports
68 | /******/ return __webpack_require__(__webpack_require__.s = 2);
69 | /******/ })
70 | /************************************************************************/
71 | /******/ ([
72 | /* 0 */
73 | /***/ (function(module, exports) {
74 |
75 | var IMPORT = "IMPORT";
76 | var EXPORT_ALL = "EXPORT_ALL";
77 | var EXPORT_SELECTED = "EXPORT_SELECTED";
78 |
79 | module.exports = {
80 | IMPORT: IMPORT,
81 | EXPORT_ALL: EXPORT_ALL,
82 | EXPORT_SELECTED: EXPORT_SELECTED
83 | };
84 |
85 | /***/ }),
86 | /* 1 */
87 | /***/ (function(module, exports, __webpack_require__) {
88 |
89 | var insertionSort = __webpack_require__(4).insertionSort;
90 |
91 | // make request to get object data from json url
92 | function getJSONFromURL(url, key) {
93 | var task = NSTask.alloc().init();
94 | task.setLaunchPath("/usr/bin/curl");
95 |
96 | var args = NSMutableArray.alloc().init();
97 | args.addObject("-v");
98 | args.addObject("POST");
99 | args.addObject("-F");
100 | args.addObject("apikey=" + key);
101 | args.addObject(url);
102 | task.setArguments(args);
103 |
104 | var outputPipe = NSPipe.pipe();
105 | task.setStandardOutput(outputPipe);
106 | task.launch();
107 |
108 | var outputData = outputPipe.fileHandleForReading();
109 | var data = outputData.readDataToEndOfFile();
110 | var classNameOfOutput = NSStringFromClass(data["class"]());
111 |
112 | if (classNameOfOutput != "_NSZeroData") {
113 | var res = NSJSONSerialization.JSONObjectWithData_options_error(data, NSJSONReadingMutableContainers, null);
114 |
115 | if (res != null) {
116 | if (res.status == 1) {
117 | //success
118 | //log(res.content);
119 | return res.content;
120 | } else {
121 | return null;
122 | }
123 | }
124 | } else {
125 | return null;
126 | }
127 | }
128 |
129 | //retrive image data from url
130 | function getImageFromURL(url, ingnoreCache) {
131 | var request = ingnoreCache ? NSURLRequest.requestWithURL_cachePolicy_timeoutInterval(NSURL.URLWithString(url), NSURLRequestReloadIgnoringLocalCacheData, 60) : NSURLRequest.requestWithURL(NSURL.URLWithString(url));
132 |
133 | var responsePtr = MOPointer.alloc().init();
134 | var errorPtr = MOPointer.alloc().init();
135 | var data = NSURLConnection.sendSynchronousRequest_returningResponse_error(request, responsePtr, errorPtr);
136 |
137 | if (errorPtr.value() != null) {
138 | return null;
139 | }
140 |
141 | var response = responsePtr.value();
142 |
143 | if (response.statusCode() != 200) {
144 | return null;
145 | }
146 |
147 | var mimeType = response.allHeaderFields()["Content-Type"];
148 |
149 | if (!mimeType || !mimeType.hasPrefix("image/")) {
150 | return null;
151 | }
152 |
153 | return NSImage.alloc().initWithData(data);
154 | }
155 |
156 | function organiseData(object) {
157 | var flattenByKey = function flattenByKey(object, property) {
158 | return Object.keys(object[property]).map(function (key) {
159 | return object[property][key];
160 | });
161 | };
162 | data = {};
163 | data.ifaces = object.ifaces;
164 | data.groups = flattenByKey(object, "groups");
165 |
166 | data.groups = insertionSort(data.groups);
167 | data.groups.forEach(function (group) {
168 | if (group.uc) {
169 | var tempUc = flattenByKey(group, "uc");
170 | group.uc = insertionSort(tempUc);
171 | group.uc.forEach(function (uc) {
172 | uc.flow = flattenByKey(uc, "flow");
173 | // we don't order flows by number (for now)
174 | uc.flow.forEach(function (flow) {
175 | if (flow.step) {
176 | var tempStep = flattenByKey(flow, "step");
177 | flow.step = insertionSort(tempStep);
178 | flow.step.forEach(function (step) {
179 | if (step.object) {
180 | step.object = flattenByKey(step, "object");
181 | step.object = step.object.find(function (obj) {
182 | return obj.object_type == "12";
183 | });
184 | }
185 | });
186 | }
187 | });
188 | });
189 | }
190 | });
191 |
192 | // log(data);
193 | return data;
194 | }
195 |
196 | function logRequest(key, message, isImport) {
197 | var task = NSTask.alloc().init();
198 | task.setLaunchPath("/usr/bin/curl");
199 |
200 | var typeNumber = isImport ? 0 : 1;
201 |
202 | var args = NSMutableArray.alloc().init();
203 | args.addObject("-v");
204 | args.addObject("POST");
205 | args.addObject("-F");
206 | args.addObject("message=" + message);
207 | args.addObject("https://www.reqfire.com/app/project/sketchevent/type/" + typeNumber + "/ext/" + key);
208 | task.setArguments(args);
209 |
210 | var outputPipe = NSPipe.pipe();
211 | task.setStandardOutput(outputPipe);
212 | task.launch();
213 | }
214 |
215 | module.exports = {
216 | getJSONFromURL: getJSONFromURL,
217 | getImageFromURL: getImageFromURL,
218 | organiseData: organiseData,
219 | logRequest: logRequest
220 | };
221 |
222 | /***/ }),
223 | /* 2 */
224 | /***/ (function(module, exports, __webpack_require__) {
225 |
226 | Object.defineProperty(exports, "__esModule", {
227 | value: true
228 | });
229 |
230 | exports["default"] = function (context) {
231 | // call the function with allSymbos === true
232 | return exportInterfaces(context, true);
233 | };
234 |
235 | var exportInterfaces = __webpack_require__(3).exportInterfaces;
236 |
237 | /***/ }),
238 | /* 3 */
239 | /***/ (function(module, exports, __webpack_require__) {
240 |
241 | var getJSONFromURL = __webpack_require__(1).getJSONFromURL;
242 | var getImageFromURL = __webpack_require__(1).getImageFromURL;
243 | var organiseData = __webpack_require__(1).organiseData;
244 | var logRequest = __webpack_require__(1).logRequest;
245 | var getInputFromUser = __webpack_require__(5).getInputFromUser;
246 | var EXPORT_ALL = __webpack_require__(0).EXPORT_ALL;
247 | var EXPORT_SELECTED = __webpack_require__(0).EXPORT_SELECTED;
248 |
249 | function exportInterfaces(context, exportAllSymbols) {
250 | var sketch = context.api();
251 | var doc = context.document;
252 | var pages = doc.pages();
253 | var projectImportURL = "https://www.reqfire.com/app/project/sketchexport/";
254 |
255 | var app = NSApplication.sharedApplication();
256 | // Get file path
257 | var manifestPath = context.plugin.url().URLByAppendingPathComponent("Contents").URLByAppendingPathComponent("Sketch").URLByAppendingPathComponent("manifest.json").path();
258 |
259 | // Get file content
260 | var manifest = NSJSONSerialization.JSONObjectWithData_options_error(NSData.dataWithContentsOfFile(manifestPath), 0, nil);
261 | var version = manifest.version;
262 |
263 | var layersToExport;
264 | if (exportAllSymbols) {
265 | layersToExport = getLayers(context, pages);
266 | } else {
267 | layersToExport = checkSymbols(context.selection);
268 | if (context.selection.length < 1) {
269 | app.displayDialog_withTitle("Please ensure you have at least one symbol artboard selected.", "Error — No symbol artboard selected for sync");
270 | return null;
271 | } else {
272 | if (layersToExport.length < 1) {
273 | app.displayDialog_withTitle("Please ensure the symbol artboards selected aren't empty.", "Error — Unable to sync current selection");
274 | return null;
275 | }
276 | }
277 | }
278 | if (layersToExport.length == 0) {
279 | app.displayDialog_withTitle("No need to export", "No Update Found");
280 | return null;
281 | }
282 |
283 | // Get the user's project API key. Pass true for export
284 | var type = exportAllSymbols ? EXPORT_ALL : EXPORT_SELECTED;
285 | var apiKey = getInputFromUser(context, type);
286 | if (!apiKey || apiKey.isEqualToString("")) {
287 | // TODO: prompt that they've cancelled.
288 | return null;
289 | }
290 | // Get JSON object
291 | var jsonResponse = getJSONFromURL(projectImportURL, apiKey);
292 | if (!jsonResponse) {
293 | app.displayDialog_withTitle("Please ensure you are using a valid API key.\nThis can be found within your project at Reqfire > Export > Sync to Sketch.", "Error — No API key was entered");
294 | logRequest(apiKey, "Invalid API Key", false);
295 | return null;
296 | }
297 |
298 | if (!version.isEqualToString(jsonResponse.version)) {
299 | app.displayDialog_withTitle("You can download the latest version from https://github.com/reqfire/sketch-user-flows/", "Error — Plugin version is not up to date");
300 | logRequest(apiKey, "Plugin out of date", false);
301 | log("wrong version");
302 | return null;
303 | }
304 |
305 | var projectName = jsonResponse.project.name; // get current project name
306 |
307 | var organisedData = organiseData(jsonResponse);
308 |
309 | // Temporary image path
310 | var exportPath = NSTemporaryDirectory() + "sketch-reqfire-export/";
311 |
312 | // Convert all symbols into exportable image
313 | var exportInfoList = layersToPNG(context, layersToExport);
314 |
315 | var flag;
316 | for (var i = 0; i < exportInfoList.length; i++) {
317 | for (var j = 0; j < organisedData.ifaces.length; j++) {
318 | var iName = organisedData.ifaces[j].name;
319 | var iPid = organisedData.ifaces[j].persistent_id;
320 | var X = exportInfoList[i].layerName;
321 | var id = X.split(" id:")[1];
322 |
323 | if (id == organisedData.ifaces[j].persistent_id) {
324 | flag = 1; // symbol ID exists in object ifaces
325 | break;
326 | } else {
327 | flag = -1;
328 | }
329 | }
330 |
331 | if (flag == -1) {
332 | // symbol does not match ifaces
333 | exportInfoList.splice(i, 1); // remove item out of the export list
334 | print("failed");
335 | }
336 | }
337 |
338 | //upload image to server
339 | if (getConfirmationFromUser(exportInfoList)) {
340 | var uploadStatus = updateLayer(exportInfoList, apiKey);
341 |
342 | if (uploadStatus == 1) {
343 | app.displayDialog_withTitle("Please refresh your Reqfire project to see the changes.", "Success — Your project has been synced successfully");
344 | } else if (uploadStatus == 2) {
345 | app.displayDialog_withTitle("Please ensure you are using a valid API key.\nThis can be found within your project at Reqfire > Export > Sync to Sketch.", "Error — Invalid API key provided");
346 | } else if (uploadStatus == 3) {
347 | // TODO - delete this
348 | app.displayDialog_withTitle("Unmatched project key and api key!", "Unable to Export");
349 | } else {
350 | app.displayDialog_withTitle("Error!", "Unable to Export");
351 | }
352 | } else {
353 | log("User has cancelled Export All.");
354 | // app.displayDialogue_withTitle("Export cancelled");
355 | }
356 |
357 | // return a list of symbols need to be exported
358 | function getLayers(context) {
359 | var loopSymbolMasters = context.document.documentData().allSymbols().objectEnumerator();
360 | var symbolMaster;
361 | var layers = []; // a list to save exportable symbol
362 |
363 | while (symbolMaster = loopSymbolMasters.nextObject()) {
364 | // symbol children count equals one when there is no child in the symbol.
365 | // only symbols has children can be added into the list
366 | if (symbolMaster.children().count() != 1) {
367 | layers.push(symbolMaster);
368 | }
369 | }
370 | return layers;
371 | }
372 |
373 | // convert layer into image
374 | function layersToPNG(context, layers) {
375 | var exportInfoList = []; // prepare list with export layer info
376 | for (var i = 0; i < layers.length; i++) {
377 | var mslayer = layers[i];
378 | var layerID = mslayer.objectID();
379 |
380 | // store image into a temp path
381 | var filePath = exportPath + layerID + ".png";
382 | var exportRequest = MSExportRequest.exportRequestsFromExportableLayer(mslayer).firstObject();
383 |
384 | context.document.saveArtboardOrSlice_toFile(exportRequest, filePath); // save layer to file
385 | var exportInfo = {
386 | layerID: layerID,
387 | layerName: mslayer.name(),
388 | layer: mslayer,
389 | path: filePath
390 | };
391 |
392 | exportInfoList.push(exportInfo);
393 | }
394 | return exportInfoList;
395 | }
396 |
397 | //upload symbols
398 | function updateLayer(list, apiKey) {
399 | var fullURl = "https://www.reqfire.com/app/component/sketchimport";
400 | var task = NSTask.alloc().init();
401 | var uploadStatus;
402 | task.setLaunchPath("/usr/bin/curl");
403 | var args = NSMutableArray.alloc().init();
404 | args.addObject("-v");
405 | args.addObject("POST");
406 | args.addObject("--header");
407 | args.addObject("Content-Type: multipart/form-data");
408 |
409 | for (var i = 0; i < list.length; i++) {
410 | args.addObject("-F");
411 | args.addObject(list[i]["layerID"] + "=" + list[i]["layerName"]);
412 | args.addObject("-F");
413 | args.addObject(list[i]["layerName"].split(" id:")[1] + "=@" + list[i]["path"]);
414 | }
415 |
416 | args.addObject("-F");
417 | args.addObject("apikey=" + apiKey);
418 | args.addObject(fullURl);
419 | task.setArguments(args);
420 | var outputPipe = NSPipe.pipe();
421 | task.setStandardOutput(outputPipe);
422 | task.launch();
423 | var outputData = outputPipe.fileHandleForReading();
424 | var data = outputData.readDataToEndOfFile();
425 | var classNameOfOutput = NSStringFromClass(data["class"]());
426 |
427 | if (classNameOfOutput != "_NSZeroData") {
428 | var res = NSJSONSerialization.JSONObjectWithData_options_error(data, NSJSONReadingMutableLeaves, nil);
429 |
430 | if (res != null) {
431 | if (res.error == nil) {
432 | uploadStatus = res.status;
433 | }
434 | }
435 | } else {
436 | uploadStatus = 0;
437 | }
438 | print(res);
439 | return uploadStatus;
440 | }
441 |
442 | function getConfirmationFromUser(exportInfoList) {
443 | var alert = NSAlert.alloc().init();
444 | var plural = exportInfoList.length == 1 ? "" : "s";
445 |
446 | alert.setMessageText("Confirming Project Sync");
447 | alert.setInformativeText(exportInfoList.length + " symbol" + plural + " (containing content) selected to be synced with — " + projectName);
448 | alert.addButtonWithTitle("Ok");
449 | alert.addButtonWithTitle("Cancel");
450 |
451 | return alert.runModal() == NSAlertFirstButtonReturn;
452 | }
453 |
454 | function checkSymbols(layers) {
455 | var flag = 0;
456 | var layerList = []; // a list to save exportable symbol
457 | layers.forEach(function (layer) {
458 | // check the selected layer is symbol ot not
459 | if (layer instanceof MSSymbolMaster) {
460 | // symbol children count equals one when there is no child in the symbol.
461 | // only symbols has children can be added into the list
462 | if (layer.children().count() != 1) {
463 | layerList.push(layer);
464 | }
465 | }
466 | });
467 | return layerList;
468 | }
469 | }
470 |
471 | module.exports = {
472 | exportInterfaces: exportInterfaces
473 | };
474 |
475 | /***/ }),
476 | /* 4 */
477 | /***/ (function(module, exports) {
478 |
479 | function insertionSort(items) {
480 | var len = items.length,
481 | value,
482 | i,
483 | j;
484 |
485 | for (i = 0; i < len; i++) {
486 | // store the current value because it may shift later
487 | value = items[i];
488 | for (j = i - 1; j > -1 && Number(items[j].number) > Number(value.number); j--) {
489 | items[j + 1] = items[j];
490 | }
491 | items[j + 1] = value;
492 | }
493 | return items;
494 | }
495 |
496 | module.exports = {
497 | insertionSort: insertionSort
498 | };
499 |
500 | /***/ }),
501 | /* 5 */
502 | /***/ (function(module, exports, __webpack_require__) {
503 |
504 | var IMPORT = __webpack_require__(0).IMPORT;
505 | var EXPORT_ALL = __webpack_require__(0).EXPORT_ALL;
506 | var EXPORT_SELECTED = __webpack_require__(0).EXPORT_SELECTED;
507 |
508 | function getInputFromUser(context, type) {
509 | var window = type === IMPORT ? createImportWindow(context) : createExportWindow(context, type);
510 | var alert = window[0];
511 | var response = alert.runModal();
512 | if (response == "1000") {
513 | //save user input to user defaults to enable auto filled text field
514 | // userDefaults.setObject_forKey(apiKey, "apiKey");
515 | // userDefaults.synchronize();
516 | return jsonTextField.stringValue();
517 | } else {
518 | return false;
519 | }
520 | }
521 |
522 | function createImportWindow(context) {
523 | userDefaults = NSUserDefaults.alloc().initWithSuiteName("com.bohemiancoding.sketch.exportSelectedInterface");
524 |
525 | var alert = COSAlertWindow["new"]();
526 | alert.setMessageText("Import Reqfire Project");
527 | alert.addButtonWithTitle("Confirm");
528 | alert.addButtonWithTitle("Cancel");
529 | var viewWidth = 400;
530 | var viewHeight = 150;
531 | var view = NSView.alloc().initWithFrame(NSMakeRect(0, 0, viewWidth, viewHeight));
532 | alert.addAccessoryView(view);
533 |
534 | var linkLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 55, viewWidth - 100, 35));
535 | linkLabel.setStringValue("Your Reqfire Project API key can be found within your project at Reqfire > Export > Sync to Sketch.");
536 | linkLabel.setSelectable(false);
537 | linkLabel.setEditable(false);
538 | linkLabel.setBezeled(false);
539 | linkLabel.setDrawsBackground(false);
540 |
541 | var jsonLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 85, viewWidth - 100, 20));
542 | jsonLabel.setStringValue("Enter your API key");
543 | jsonLabel.setSelectable(false);
544 | jsonLabel.setEditable(false);
545 | jsonLabel.setBezeled(false);
546 | jsonLabel.setDrawsBackground(false);
547 |
548 | view.addSubview(linkLabel);
549 | view.addSubview(jsonLabel);
550 |
551 | jsonTextField = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 105, 300, 20));
552 | view.addSubview(jsonTextField);
553 |
554 | return [alert];
555 | }
556 |
557 | function createExportWindow(context, type) {
558 | // Creates a user defaults object initialized with the defaults for the specified domain name
559 | userDefaults = NSUserDefaults.alloc().initWithSuiteName("com.bohemiancoding.sketch.exportAllInterface");
560 |
561 | var alert = COSAlertWindow["new"]();
562 | if (type === EXPORT_ALL) {
563 | alert.setMessageText("Sync All Artboards");
564 | } else if (type === EXPORT_SELECTED) {
565 | alert.setMessageText("Sync Selected Artboards");
566 | }
567 | alert.addButtonWithTitle("Confirm");
568 | alert.addButtonWithTitle("Cancel");
569 |
570 | var viewWidth = 400;
571 | var viewHeight = 160;
572 | var view = NSView.alloc().initWithFrame(NSMakeRect(0, 0, viewWidth, viewHeight));
573 | alert.addAccessoryView(view);
574 |
575 | var alertLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 55, viewWidth - 100, 50));
576 | var infoLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 95, viewWidth - 100, 35));
577 | var jsonLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 120, viewWidth - 100, 20));
578 |
579 | alertLabel.setStringValue("Note: Any symbols you create that are not sitting inside the symbol artboards generated by this plugin will not be synced up to your Reqfire project.");
580 | alertLabel.setSelectable(false);
581 | alertLabel.setEditable(false);
582 | alertLabel.setBezeled(false);
583 | alertLabel.setDrawsBackground(false);
584 |
585 | infoLabel.setStringValue("Your Reqfire Project API key can be found within your project at Reqfire > Export > Sync to Sketch.");
586 | infoLabel.setSelectable(false);
587 | infoLabel.setEditable(false);
588 | infoLabel.setBezeled(false);
589 | infoLabel.setDrawsBackground(false);
590 |
591 | jsonLabel.setStringValue("Enter your API key");
592 | jsonLabel.setSelectable(false);
593 | jsonLabel.setEditable(false);
594 | jsonLabel.setBezeled(false);
595 | jsonLabel.setDrawsBackground(false);
596 |
597 | view.addSubview(alertLabel);
598 | view.addSubview(infoLabel);
599 | view.addSubview(jsonLabel);
600 |
601 | jsonTextField = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 140, 300, 20));
602 | jsonTextField.setStringValue("");
603 | view.addSubview(jsonTextField);
604 |
605 | return [alert];
606 | }
607 |
608 | module.exports = {
609 | getInputFromUser: getInputFromUser,
610 | createImportWindow: createImportWindow,
611 | createExportWindow: createExportWindow
612 | };
613 |
614 | /***/ })
615 | /******/ ]);
616 | if (key === 'default' && typeof exports === 'function') {
617 | exports(context);
618 | } else {
619 | exports[key](context);
620 | }
621 | }
622 | that['onRun'] = __skpm_run.bind(this, 'default')
623 |
--------------------------------------------------------------------------------
/sketch_user_flows.sketchplugin/Contents/Sketch/exportSelectedInterface.js:
--------------------------------------------------------------------------------
1 | var that = this;
2 | function __skpm_run (key, context) {
3 | that.context = context;
4 |
5 | var exports =
6 | /******/ (function(modules) { // webpackBootstrap
7 | /******/ // The module cache
8 | /******/ var installedModules = {};
9 | /******/
10 | /******/ // The require function
11 | /******/ function __webpack_require__(moduleId) {
12 | /******/
13 | /******/ // Check if module is in cache
14 | /******/ if(installedModules[moduleId]) {
15 | /******/ return installedModules[moduleId].exports;
16 | /******/ }
17 | /******/ // Create a new module (and put it into the cache)
18 | /******/ var module = installedModules[moduleId] = {
19 | /******/ i: moduleId,
20 | /******/ l: false,
21 | /******/ exports: {}
22 | /******/ };
23 | /******/
24 | /******/ // Execute the module function
25 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
26 | /******/
27 | /******/ // Flag the module as loaded
28 | /******/ module.l = true;
29 | /******/
30 | /******/ // Return the exports of the module
31 | /******/ return module.exports;
32 | /******/ }
33 | /******/
34 | /******/
35 | /******/ // expose the modules object (__webpack_modules__)
36 | /******/ __webpack_require__.m = modules;
37 | /******/
38 | /******/ // expose the module cache
39 | /******/ __webpack_require__.c = installedModules;
40 | /******/
41 | /******/ // define getter function for harmony exports
42 | /******/ __webpack_require__.d = function(exports, name, getter) {
43 | /******/ if(!__webpack_require__.o(exports, name)) {
44 | /******/ Object.defineProperty(exports, name, {
45 | /******/ configurable: false,
46 | /******/ enumerable: true,
47 | /******/ get: getter
48 | /******/ });
49 | /******/ }
50 | /******/ };
51 | /******/
52 | /******/ // getDefaultExport function for compatibility with non-harmony modules
53 | /******/ __webpack_require__.n = function(module) {
54 | /******/ var getter = module && module.__esModule ?
55 | /******/ function getDefault() { return module['default']; } :
56 | /******/ function getModuleExports() { return module; };
57 | /******/ __webpack_require__.d(getter, 'a', getter);
58 | /******/ return getter;
59 | /******/ };
60 | /******/
61 | /******/ // Object.prototype.hasOwnProperty.call
62 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
63 | /******/
64 | /******/ // __webpack_public_path__
65 | /******/ __webpack_require__.p = "";
66 | /******/
67 | /******/ // Load entry module and return exports
68 | /******/ return __webpack_require__(__webpack_require__.s = 2);
69 | /******/ })
70 | /************************************************************************/
71 | /******/ ([
72 | /* 0 */
73 | /***/ (function(module, exports, __webpack_require__) {
74 |
75 | var insertionSort = __webpack_require__(3).insertionSort;
76 |
77 | // make request to get object data from json url
78 | function getJSONFromURL(url, key) {
79 | var task = NSTask.alloc().init();
80 | task.setLaunchPath("/usr/bin/curl");
81 |
82 | var args = NSMutableArray.alloc().init();
83 | args.addObject("-v");
84 | args.addObject("POST");
85 | args.addObject("-F");
86 | args.addObject("apikey=" + key);
87 | args.addObject(url);
88 | task.setArguments(args);
89 |
90 | var outputPipe = NSPipe.pipe();
91 | task.setStandardOutput(outputPipe);
92 | task.launch();
93 |
94 | var outputData = outputPipe.fileHandleForReading();
95 | var data = outputData.readDataToEndOfFile();
96 | var classNameOfOutput = NSStringFromClass(data["class"]());
97 |
98 | if (classNameOfOutput != "_NSZeroData") {
99 | var res = NSJSONSerialization.JSONObjectWithData_options_error(data, NSJSONReadingMutableContainers, null);
100 |
101 | if (res != null) {
102 | if (res.status == 1) {
103 | //success
104 | //log(res.content);
105 | return res.content;
106 | } else {
107 | return null;
108 | }
109 | }
110 | } else {
111 | return null;
112 | }
113 | }
114 |
115 | //retrive image data from url
116 | function getImageFromURL(url, ingnoreCache) {
117 | var request = ingnoreCache ? NSURLRequest.requestWithURL_cachePolicy_timeoutInterval(NSURL.URLWithString(url), NSURLRequestReloadIgnoringLocalCacheData, 60) : NSURLRequest.requestWithURL(NSURL.URLWithString(url));
118 |
119 | var responsePtr = MOPointer.alloc().init();
120 | var errorPtr = MOPointer.alloc().init();
121 | var data = NSURLConnection.sendSynchronousRequest_returningResponse_error(request, responsePtr, errorPtr);
122 |
123 | if (errorPtr.value() != null) {
124 | return null;
125 | }
126 |
127 | var response = responsePtr.value();
128 |
129 | if (response.statusCode() != 200) {
130 | return null;
131 | }
132 |
133 | var mimeType = response.allHeaderFields()["Content-Type"];
134 |
135 | if (!mimeType || !mimeType.hasPrefix("image/")) {
136 | return null;
137 | }
138 |
139 | return NSImage.alloc().initWithData(data);
140 | }
141 |
142 | function organiseData(object) {
143 | var flattenByKey = function flattenByKey(object, property) {
144 | return Object.keys(object[property]).map(function (key) {
145 | return object[property][key];
146 | });
147 | };
148 | data = {};
149 | data.ifaces = object.ifaces;
150 | data.groups = flattenByKey(object, "groups");
151 |
152 | data.groups = insertionSort(data.groups);
153 | data.groups.forEach(function (group) {
154 | if (group.uc) {
155 | var tempUc = flattenByKey(group, "uc");
156 | group.uc = insertionSort(tempUc);
157 | group.uc.forEach(function (uc) {
158 | uc.flow = flattenByKey(uc, "flow");
159 | // we don't order flows by number (for now)
160 | uc.flow.forEach(function (flow) {
161 | if (flow.step) {
162 | var tempStep = flattenByKey(flow, "step");
163 | flow.step = insertionSort(tempStep);
164 | flow.step.forEach(function (step) {
165 | if (step.object) {
166 | step.object = flattenByKey(step, "object");
167 | step.object = step.object.find(function (obj) {
168 | return obj.object_type == "12";
169 | });
170 | }
171 | });
172 | }
173 | });
174 | });
175 | }
176 | });
177 |
178 | // log(data);
179 | return data;
180 | }
181 |
182 | function logRequest(key, message, isImport) {
183 | var task = NSTask.alloc().init();
184 | task.setLaunchPath("/usr/bin/curl");
185 |
186 | var typeNumber = isImport ? 0 : 1;
187 |
188 | var args = NSMutableArray.alloc().init();
189 | args.addObject("-v");
190 | args.addObject("POST");
191 | args.addObject("-F");
192 | args.addObject("message=" + message);
193 | args.addObject("https://www.reqfire.com/app/project/sketchevent/type/" + typeNumber + "/ext/" + key);
194 | task.setArguments(args);
195 |
196 | var outputPipe = NSPipe.pipe();
197 | task.setStandardOutput(outputPipe);
198 | task.launch();
199 | }
200 |
201 | module.exports = {
202 | getJSONFromURL: getJSONFromURL,
203 | getImageFromURL: getImageFromURL,
204 | organiseData: organiseData,
205 | logRequest: logRequest
206 | };
207 |
208 | /***/ }),
209 | /* 1 */
210 | /***/ (function(module, exports) {
211 |
212 | var IMPORT = "IMPORT";
213 | var EXPORT_ALL = "EXPORT_ALL";
214 | var EXPORT_SELECTED = "EXPORT_SELECTED";
215 |
216 | module.exports = {
217 | IMPORT: IMPORT,
218 | EXPORT_ALL: EXPORT_ALL,
219 | EXPORT_SELECTED: EXPORT_SELECTED
220 | };
221 |
222 | /***/ }),
223 | /* 2 */
224 | /***/ (function(module, exports, __webpack_require__) {
225 |
226 | Object.defineProperty(exports, "__esModule", {
227 | value: true
228 | });
229 |
230 | exports["default"] = function (context) {
231 | return exportInterfaces(context, false);
232 | };
233 |
234 | var getJSONFromURL = __webpack_require__(0).getJSONFromURL;
235 |
236 |
237 | // var sketch = context.api();
238 | // var doc = context.document;
239 | // var pages = doc.pages();
240 | // var uploadStatus;
241 | // var jsonInput;
242 | // const jsonUrl = "https://www.reqfire.com/app/project/sketchexport/";
243 | // var app = NSApplication.sharedApplication();
244 | // var userDefaults;
245 | // var layers = context.selection;
246 |
247 | // if (layers.length < 1) {
248 | // app.displayDialog_withTitle(
249 | // "Please ensure you have at least one symbol artboard selected.",
250 | // "Error — No symbol artboard selected for sync"
251 | // );
252 | // } else {
253 | // if (checkSymbols(layers).length < 1) {
254 | // app.displayDialog_withTitle(
255 | // "Please ensure the symbol artboards selected aren't empty.",
256 | // "Error — Unable to sync current selection"
257 | // );
258 | // } else {
259 | // if (getInputFromUser(context)) {
260 | // logRequest(jsonInput);
261 | // var obj = getJSONFromURL(jsonUrl, jsonInput);
262 |
263 | // //get file path
264 | // var manifestPath = context.plugin.url().URLByAppendingPathComponent("Contents").URLByAppendingPathComponent("Sketch").URLByAppendingPathComponent("manifest.json").path();
265 | // //get file content
266 | // var manifest = NSJSONSerialization.JSONObjectWithData_options_error(
267 | // NSData.dataWithContentsOfFile(manifestPath),
268 | // 0,
269 | // nil
270 | // );
271 | // var version = manifest.version;
272 |
273 | // var exportPath = NSTemporaryDirectory() + "sketch-reqfire-export/"; // set a temporary path
274 | // var exportInfoList = layersToPNG(context, checkSymbols(layers));
275 |
276 | // if (obj) {
277 | // projectName = obj.project.name;
278 | // if (version == obj.version) {
279 | // obj = convertData(obj);
280 | // var flag;
281 | // for (var i = 0; i < exportInfoList.length; i++) {
282 | // for (var j = 0; j < obj.ifaces.length; j++) {
283 | // var iPid = obj.ifaces[j].persistent_id;
284 | // var X = exportInfoList[i].layerName;
285 | // var id = X.split(" id:")[1];
286 | // if (id == obj.ifaces[j].persistent_id) {
287 | // flag = 1;
288 | // break;
289 | // } else {
290 | // flag = -1;
291 | // }
292 | // }
293 | // if (flag == -1) {
294 | // exportInfoList.splice(i, 1);
295 | // print("failed");
296 | // }
297 | // }
298 |
299 | // if (getConfirmationFromUser(exportInfoList)) {
300 | // updateLayer(exportInfoList);
301 |
302 | // if (uploadStatus == 1) {
303 | // app.displayDialog_withTitle(
304 | // "Please refresh your Reqfire project to see the changes.",
305 | // "Success — Your project has been synced successfully"
306 | // );
307 | // } else if (uploadStatus == 2) {
308 | // app.displayDialog_withTitle(
309 | // "Please ensure you are using a valid API key.\nThis can be found within your project at Reqfire > Export > Sync to Sketch.",
310 | // "Error — Invalid API key provided"
311 | // );
312 | // } else if (uploadStatus == 3) {
313 | // // TODO - delete this
314 | // app.displayDialog_withTitle(
315 | // "Unmatched project key and api key!",
316 | // "Unable to Export"
317 | // );
318 | // } else {
319 | // app.displayDialog_withTitle("Error!", "Unable to Export");
320 | // }
321 | // print(layers.length);
322 | // } else {
323 | // log("User has cancelled Export Selected.")
324 | // // app.displayDialogue_withTitle("Export cancelled");
325 | // }
326 | // } else {
327 | // app.displayDialog_withTitle(
328 | // "You can download the latest version from https://github.com/reqfire/sketch-user-flows/",
329 | // "Error — Plugin version is not up to date"
330 | // );
331 | // }
332 | // } else {
333 | // app.displayDialog_withTitle(
334 | // "Please ensure you are using a valid API key.\nThis can be found within your project at Reqfire > Export > Sync to Sketch.",
335 | // "Error — No project found"
336 | // );
337 | // }
338 | // } else {
339 | // log("cancelled");
340 | // }
341 | // }
342 | // }
343 |
344 | // function layersToPNG(context, layers) {
345 | // var exportInfoList = []; // prepare list with export layer info
346 |
347 | // for (var i = 0; i < layers.length; i++) {
348 | // var mslayer = layers[i];
349 | // var layerID = mslayer.objectID();
350 | // // store image into a temp path
351 | // var filePath = exportPath + layerID + ".png";
352 | // var exportRequest = MSExportRequest.exportRequestsFromExportableLayer(
353 | // mslayer
354 | // ).firstObject();
355 |
356 | // context.document.saveArtboardOrSlice_toFile(exportRequest, filePath); // save layer to file
357 |
358 | // var exportInfo = {
359 | // layerID: layerID,
360 | // layerName: mslayer.name(),
361 | // layer: mslayer,
362 | // path: filePath
363 | // };
364 |
365 | // exportInfoList.push(exportInfo);
366 | // }
367 |
368 | // return exportInfoList;
369 | // }
370 |
371 | // function updateLayer(list) {
372 | // var fullURl = "https://www.reqfire.com/app/component/sketchimport";
373 | // var task = NSTask.alloc().init();
374 | // task.setLaunchPath("/usr/bin/curl");
375 |
376 | // var args = NSMutableArray.alloc().init();
377 | // args.addObject("-v");
378 | // args.addObject("POST");
379 | // args.addObject("--header");
380 | // args.addObject("Content-Type: multipart/form-data");
381 |
382 | // for (var i = 0; i < list.length; i++) {
383 | // args.addObject("-F");
384 | // args.addObject(list[i]["layerID"] + "=" + list[i]["layerName"]);
385 | // args.addObject("-F");
386 | // args.addObject(
387 | // list[i]["layerName"].split(" id:")[1] + "=@" + list[i]["path"]
388 | // );
389 | // }
390 |
391 | // args.addObject("-F");
392 | // args.addObject("apikey=" + jsonInput);
393 | // args.addObject(fullURl);
394 | // task.setArguments(args);
395 | // var outputPipe = NSPipe.pipe();
396 | // task.setStandardOutput(outputPipe);
397 | // task.launch();
398 |
399 | // var outputData = outputPipe.fileHandleForReading();
400 | // var data = outputData.readDataToEndOfFile();
401 | // var classNameOfOutput = NSStringFromClass(data.class());
402 |
403 | // if (classNameOfOutput != "_NSZeroData") {
404 | // var res = NSJSONSerialization.JSONObjectWithData_options_error(
405 | // data,
406 | // NSJSONReadingMutableLeaves,
407 | // nil
408 | // );
409 |
410 | // if (res != null) {
411 | // uploadStatus = res.status;
412 | // }
413 | // } else {
414 | // uploadStatus = 0;
415 | // }
416 | // print(res);
417 | // }
418 |
419 | // // read data from json url
420 | // function getJSONFromURL(url, key) {
421 | // var task = NSTask.alloc().init();
422 | // task.setLaunchPath("/usr/bin/curl");
423 |
424 | // var args = NSMutableArray.alloc().init();
425 | // args.addObject("-v");
426 | // args.addObject("POST");
427 | // args.addObject("-F");
428 | // args.addObject("apikey=" + key);
429 | // args.addObject(url);
430 | // task.setArguments(args);
431 | // var outputPipe = NSPipe.pipe();
432 | // task.setStandardOutput(outputPipe);
433 | // task.launch();
434 |
435 | // var outputData = outputPipe.fileHandleForReading();
436 | // var data = outputData.readDataToEndOfFile();
437 | // var classNameOfOutput = NSStringFromClass(data.class());
438 |
439 | // if (classNameOfOutput != "_NSZeroData") {
440 | // var res = NSJSONSerialization.JSONObjectWithData_options_error(
441 | // data,
442 | // NSJSONReadingMutableContainers,
443 | // null
444 | // );
445 |
446 | // if (res != null) {
447 | // log(res.status);
448 | // if (res.status == 1) {
449 | // return res.content;
450 | // } else {
451 | // return null;
452 | // }
453 | // // TODO error handling
454 | // }
455 | // } else {
456 | // return null;
457 | // }
458 | // // var res =NSString.alloc().initWithData_encoding(data,NSUTF8StringEncoding)
459 | // }
460 |
461 | // function convertData(object) {
462 | // const flattenByKey = function(object, property) {
463 | // return Object.keys(object[property]).map(function(key) {
464 | // return object[property][key];
465 | // });
466 | // };
467 |
468 | // data = {};
469 | // data.ifaces = object.ifaces;
470 | // data.groups = flattenByKey(object, "groups");
471 |
472 | // data.groups.forEach(function(group) {
473 | // if (group.uc) {
474 | // group.uc = flattenByKey(group, "uc");
475 | // group.uc.forEach(function(uc) {
476 | // uc.flow = flattenByKey(uc, "flow");
477 | // uc.flow.forEach(function(flow) {
478 | // if (flow.step) {
479 | // flow.step = flattenByKey(flow, "step");
480 | // flow.step.forEach(function(step) {
481 | // if (step.object) {
482 | // step.object = flattenByKey(step, "object");
483 | // step.object = step.object.find(function(obj) {
484 | // return obj.object_type == "12";
485 | // });
486 | // }
487 | // });
488 | // }
489 | // });
490 | // });
491 | // }
492 | // });
493 | // return data;
494 | // }
495 |
496 | // //check selection is symbol or not, and check if it is symbol with no child
497 | // function checkSymbols(layers) {
498 | // var flag = 0;
499 | // var layerList = []; // a list to save exportable symbol
500 | // layers.forEach(function(layer) {
501 | // // check the selected layer is symbol ot not
502 | // if (layer instanceof MSSymbolMaster) {
503 | // // symbol children count equals one when there is no child in the symbol.
504 | // // only symbols has children can be added into the list
505 | // if (layer.children().count() != 1) {
506 | // layerList.push(layer);
507 | // }
508 | // }
509 | // });
510 | // return layerList;
511 | // }
512 |
513 | // function getInputFromUser(context) {
514 | // var window = createInputWindow(context);
515 | // var alert = window[0];
516 | // var response = alert.runModal();
517 | // if (response == "1000") {
518 | // jsonInput = jsonTextField.stringValue();
519 | // //save user input to user defaults to enable auto filled text field
520 | // userDefaults.setObject_forKey(jsonInput, "jsonInput");
521 | // userDefaults.synchronize();
522 | // return true;
523 | // } else {
524 | // return false;
525 | // }
526 | // }
527 |
528 | // function getConfirmationFromUser(exportInfoList) {
529 | // var alert = NSAlert.alloc().init();
530 | // var plural = exportInfoList.length == 1 ? "" : "s";
531 | // alert.setMessageText("Confirming project export");
532 | // alert.setInformativeText(
533 | // exportInfoList.length +
534 | // " Symbol" +
535 | // plural +
536 | // " selected to be synced with your Reqfire project — " + projectName
537 | // );
538 | // alert.addButtonWithTitle("Ok");
539 | // alert.addButtonWithTitle("Cancel");
540 |
541 | // return alert.runModal() == NSAlertFirstButtonReturn;
542 | // }
543 |
544 | // function createInputWindow(context) {
545 | // // Creates a user defaults object initialized with the defaults for the specified domain name
546 | // userDefaults = NSUserDefaults.alloc().initWithSuiteName(
547 | // "com.bohemiancoding.sketch.exportSelectedInterface"
548 | // );
549 |
550 | // var alert = COSAlertWindow.new();
551 | // alert.setMessageText("Enter your Reqfire project's API Key");
552 | // alert.addButtonWithTitle("Ok");
553 | // alert.addButtonWithTitle("Cancel");
554 | // var viewWidth = 400;
555 | // var viewHeight = 160;
556 | // var view = NSView.alloc().initWithFrame(
557 | // NSMakeRect(0, 0, viewWidth, viewHeight)
558 | // );
559 | // alert.addAccessoryView(view);
560 |
561 | // var alertLabel = NSTextField.alloc().initWithFrame(
562 | // NSMakeRect(0, viewHeight - 40, viewWidth - 100, 35)
563 | // );
564 | // var infoLabel = NSTextField.alloc().initWithFrame(
565 | // NSMakeRect(0, viewHeight - 80, viewWidth - 100, 35)
566 | // );
567 | // var jsonLabel = NSTextField.alloc().initWithFrame(
568 | // NSMakeRect(0, viewHeight - 105, viewWidth - 100, 20)
569 | // );
570 |
571 | // alertLabel.setStringValue(
572 | // "Note: Any symbols you create that is not sitting inside the symbol artboards generated by Sketch User Flows will not be synced up to your Reqfire project."
573 | // );
574 | // alertLabel.setSelectable(false);
575 | // alertLabel.setEditable(false);
576 | // alertLabel.setBezeled(false);
577 | // alertLabel.setDrawsBackground(false);
578 |
579 | // infoLabel.setStringValue(
580 | // "Your Reqfire Project API key can be found within your project at Reqfire > Export > Sync to Sketch."
581 | // );
582 | // infoLabel.setSelectable(false);
583 | // infoLabel.setEditable(false);
584 | // infoLabel.setBezeled(false);
585 | // infoLabel.setDrawsBackground(false);
586 |
587 | // jsonLabel.setStringValue("Enter your API key");
588 | // jsonLabel.setSelectable(false);
589 | // jsonLabel.setEditable(false);
590 | // jsonLabel.setBezeled(false);
591 | // jsonLabel.setDrawsBackground(false);
592 |
593 | // view.addSubview(alertLabel);
594 | // view.addSubview(infoLabel);
595 | // view.addSubview(jsonLabel);
596 |
597 | // jsonTextField = NSTextField.alloc().initWithFrame(
598 | // NSMakeRect(0, viewHeight - 125, 300, 20)
599 | // );
600 | // jsonTextField.setStringValue(getJsonValue(context));
601 | // view.addSubview(jsonTextField);
602 |
603 | // return [alert];
604 | // }
605 |
606 | // function getJsonValue(context) { //get value by the key
607 | // var jsonValue = userDefaults.objectForKey("jsonInput");
608 | // if (jsonValue != undefined) {
609 | // return jsonValue;
610 | // } else {
611 | // return ""; // Default value
612 | // }
613 | // }
614 |
615 | // function logRequest(key, message) {
616 | // var task = NSTask.alloc().init();
617 | // task.setLaunchPath("/usr/bin/curl");
618 |
619 | // var args = NSMutableArray.alloc().init();
620 | // args.addObject("-v");
621 | // args.addObject("POST");
622 | // args.addObject("-F");
623 | // args.addObject("message=" + message);
624 | // args.addObject("https://www.reqfire.com/app/project/sketchevent/type/0/ext/" + key);
625 | // task.setArguments(args);
626 |
627 | // var outputPipe = NSPipe.pipe();
628 | // task.setStandardOutput(outputPipe);
629 | // task.launch();
630 | // }
631 | var exportInterfaces = __webpack_require__(4).exportInterfaces;
632 |
633 | /***/ }),
634 | /* 3 */
635 | /***/ (function(module, exports) {
636 |
637 | function insertionSort(items) {
638 | var len = items.length,
639 | value,
640 | i,
641 | j;
642 |
643 | for (i = 0; i < len; i++) {
644 | // store the current value because it may shift later
645 | value = items[i];
646 | for (j = i - 1; j > -1 && Number(items[j].number) > Number(value.number); j--) {
647 | items[j + 1] = items[j];
648 | }
649 | items[j + 1] = value;
650 | }
651 | return items;
652 | }
653 |
654 | module.exports = {
655 | insertionSort: insertionSort
656 | };
657 |
658 | /***/ }),
659 | /* 4 */
660 | /***/ (function(module, exports, __webpack_require__) {
661 |
662 | var getJSONFromURL = __webpack_require__(0).getJSONFromURL;
663 | var getImageFromURL = __webpack_require__(0).getImageFromURL;
664 | var organiseData = __webpack_require__(0).organiseData;
665 | var logRequest = __webpack_require__(0).logRequest;
666 | var getInputFromUser = __webpack_require__(5).getInputFromUser;
667 | var EXPORT_ALL = __webpack_require__(1).EXPORT_ALL;
668 | var EXPORT_SELECTED = __webpack_require__(1).EXPORT_SELECTED;
669 |
670 | function exportInterfaces(context, exportAllSymbols) {
671 | var sketch = context.api();
672 | var doc = context.document;
673 | var pages = doc.pages();
674 | var projectImportURL = "https://www.reqfire.com/app/project/sketchexport/";
675 |
676 | var app = NSApplication.sharedApplication();
677 | // Get file path
678 | var manifestPath = context.plugin.url().URLByAppendingPathComponent("Contents").URLByAppendingPathComponent("Sketch").URLByAppendingPathComponent("manifest.json").path();
679 |
680 | // Get file content
681 | var manifest = NSJSONSerialization.JSONObjectWithData_options_error(NSData.dataWithContentsOfFile(manifestPath), 0, nil);
682 | var version = manifest.version;
683 |
684 | var layersToExport;
685 | if (exportAllSymbols) {
686 | layersToExport = getLayers(context, pages);
687 | } else {
688 | layersToExport = checkSymbols(context.selection);
689 | if (context.selection.length < 1) {
690 | app.displayDialog_withTitle("Please ensure you have at least one symbol artboard selected.", "Error — No symbol artboard selected for sync");
691 | return null;
692 | } else {
693 | if (layersToExport.length < 1) {
694 | app.displayDialog_withTitle("Please ensure the symbol artboards selected aren't empty.", "Error — Unable to sync current selection");
695 | return null;
696 | }
697 | }
698 | }
699 | if (layersToExport.length == 0) {
700 | app.displayDialog_withTitle("No need to export", "No Update Found");
701 | return null;
702 | }
703 |
704 | // Get the user's project API key. Pass true for export
705 | var type = exportAllSymbols ? EXPORT_ALL : EXPORT_SELECTED;
706 | var apiKey = getInputFromUser(context, type);
707 | if (!apiKey || apiKey.isEqualToString("")) {
708 | // TODO: prompt that they've cancelled.
709 | return null;
710 | }
711 | // Get JSON object
712 | var jsonResponse = getJSONFromURL(projectImportURL, apiKey);
713 | if (!jsonResponse) {
714 | app.displayDialog_withTitle("Please ensure you are using a valid API key.\nThis can be found within your project at Reqfire > Export > Sync to Sketch.", "Error — No API key was entered");
715 | logRequest(apiKey, "Invalid API Key", false);
716 | return null;
717 | }
718 |
719 | if (!version.isEqualToString(jsonResponse.version)) {
720 | app.displayDialog_withTitle("You can download the latest version from https://github.com/reqfire/sketch-user-flows/", "Error — Plugin version is not up to date");
721 | logRequest(apiKey, "Plugin out of date", false);
722 | log("wrong version");
723 | return null;
724 | }
725 |
726 | var projectName = jsonResponse.project.name; // get current project name
727 |
728 | var organisedData = organiseData(jsonResponse);
729 |
730 | // Temporary image path
731 | var exportPath = NSTemporaryDirectory() + "sketch-reqfire-export/";
732 |
733 | // Convert all symbols into exportable image
734 | var exportInfoList = layersToPNG(context, layersToExport);
735 |
736 | var flag;
737 | for (var i = 0; i < exportInfoList.length; i++) {
738 | for (var j = 0; j < organisedData.ifaces.length; j++) {
739 | var iName = organisedData.ifaces[j].name;
740 | var iPid = organisedData.ifaces[j].persistent_id;
741 | var X = exportInfoList[i].layerName;
742 | var id = X.split(" id:")[1];
743 |
744 | if (id == organisedData.ifaces[j].persistent_id) {
745 | flag = 1; // symbol ID exists in object ifaces
746 | break;
747 | } else {
748 | flag = -1;
749 | }
750 | }
751 |
752 | if (flag == -1) {
753 | // symbol does not match ifaces
754 | exportInfoList.splice(i, 1); // remove item out of the export list
755 | print("failed");
756 | }
757 | }
758 |
759 | //upload image to server
760 | if (getConfirmationFromUser(exportInfoList)) {
761 | var uploadStatus = updateLayer(exportInfoList, apiKey);
762 |
763 | if (uploadStatus == 1) {
764 | app.displayDialog_withTitle("Please refresh your Reqfire project to see the changes.", "Success — Your project has been synced successfully");
765 | } else if (uploadStatus == 2) {
766 | app.displayDialog_withTitle("Please ensure you are using a valid API key.\nThis can be found within your project at Reqfire > Export > Sync to Sketch.", "Error — Invalid API key provided");
767 | } else if (uploadStatus == 3) {
768 | // TODO - delete this
769 | app.displayDialog_withTitle("Unmatched project key and api key!", "Unable to Export");
770 | } else {
771 | app.displayDialog_withTitle("Error!", "Unable to Export");
772 | }
773 | } else {
774 | log("User has cancelled Export All.");
775 | // app.displayDialogue_withTitle("Export cancelled");
776 | }
777 |
778 | // return a list of symbols need to be exported
779 | function getLayers(context) {
780 | var loopSymbolMasters = context.document.documentData().allSymbols().objectEnumerator();
781 | var symbolMaster;
782 | var layers = []; // a list to save exportable symbol
783 |
784 | while (symbolMaster = loopSymbolMasters.nextObject()) {
785 | // symbol children count equals one when there is no child in the symbol.
786 | // only symbols has children can be added into the list
787 | if (symbolMaster.children().count() != 1) {
788 | layers.push(symbolMaster);
789 | }
790 | }
791 | return layers;
792 | }
793 |
794 | // convert layer into image
795 | function layersToPNG(context, layers) {
796 | var exportInfoList = []; // prepare list with export layer info
797 | for (var i = 0; i < layers.length; i++) {
798 | var mslayer = layers[i];
799 | var layerID = mslayer.objectID();
800 |
801 | // store image into a temp path
802 | var filePath = exportPath + layerID + ".png";
803 | var exportRequest = MSExportRequest.exportRequestsFromExportableLayer(mslayer).firstObject();
804 |
805 | context.document.saveArtboardOrSlice_toFile(exportRequest, filePath); // save layer to file
806 | var exportInfo = {
807 | layerID: layerID,
808 | layerName: mslayer.name(),
809 | layer: mslayer,
810 | path: filePath
811 | };
812 |
813 | exportInfoList.push(exportInfo);
814 | }
815 | return exportInfoList;
816 | }
817 |
818 | //upload symbols
819 | function updateLayer(list, apiKey) {
820 | var fullURl = "https://www.reqfire.com/app/component/sketchimport";
821 | var task = NSTask.alloc().init();
822 | var uploadStatus;
823 | task.setLaunchPath("/usr/bin/curl");
824 | var args = NSMutableArray.alloc().init();
825 | args.addObject("-v");
826 | args.addObject("POST");
827 | args.addObject("--header");
828 | args.addObject("Content-Type: multipart/form-data");
829 |
830 | for (var i = 0; i < list.length; i++) {
831 | args.addObject("-F");
832 | args.addObject(list[i]["layerID"] + "=" + list[i]["layerName"]);
833 | args.addObject("-F");
834 | args.addObject(list[i]["layerName"].split(" id:")[1] + "=@" + list[i]["path"]);
835 | }
836 |
837 | args.addObject("-F");
838 | args.addObject("apikey=" + apiKey);
839 | args.addObject(fullURl);
840 | task.setArguments(args);
841 | var outputPipe = NSPipe.pipe();
842 | task.setStandardOutput(outputPipe);
843 | task.launch();
844 | var outputData = outputPipe.fileHandleForReading();
845 | var data = outputData.readDataToEndOfFile();
846 | var classNameOfOutput = NSStringFromClass(data["class"]());
847 |
848 | if (classNameOfOutput != "_NSZeroData") {
849 | var res = NSJSONSerialization.JSONObjectWithData_options_error(data, NSJSONReadingMutableLeaves, nil);
850 |
851 | if (res != null) {
852 | if (res.error == nil) {
853 | uploadStatus = res.status;
854 | }
855 | }
856 | } else {
857 | uploadStatus = 0;
858 | }
859 | print(res);
860 | return uploadStatus;
861 | }
862 |
863 | function getConfirmationFromUser(exportInfoList) {
864 | var alert = NSAlert.alloc().init();
865 | var plural = exportInfoList.length == 1 ? "" : "s";
866 |
867 | alert.setMessageText("Confirming Project Sync");
868 | alert.setInformativeText(exportInfoList.length + " symbol" + plural + " (containing content) selected to be synced with — " + projectName);
869 | alert.addButtonWithTitle("Ok");
870 | alert.addButtonWithTitle("Cancel");
871 |
872 | return alert.runModal() == NSAlertFirstButtonReturn;
873 | }
874 |
875 | function checkSymbols(layers) {
876 | var flag = 0;
877 | var layerList = []; // a list to save exportable symbol
878 | layers.forEach(function (layer) {
879 | // check the selected layer is symbol ot not
880 | if (layer instanceof MSSymbolMaster) {
881 | // symbol children count equals one when there is no child in the symbol.
882 | // only symbols has children can be added into the list
883 | if (layer.children().count() != 1) {
884 | layerList.push(layer);
885 | }
886 | }
887 | });
888 | return layerList;
889 | }
890 | }
891 |
892 | module.exports = {
893 | exportInterfaces: exportInterfaces
894 | };
895 |
896 | /***/ }),
897 | /* 5 */
898 | /***/ (function(module, exports, __webpack_require__) {
899 |
900 | var IMPORT = __webpack_require__(1).IMPORT;
901 | var EXPORT_ALL = __webpack_require__(1).EXPORT_ALL;
902 | var EXPORT_SELECTED = __webpack_require__(1).EXPORT_SELECTED;
903 |
904 | function getInputFromUser(context, type) {
905 | var window = type === IMPORT ? createImportWindow(context) : createExportWindow(context, type);
906 | var alert = window[0];
907 | var response = alert.runModal();
908 | if (response == "1000") {
909 | //save user input to user defaults to enable auto filled text field
910 | // userDefaults.setObject_forKey(apiKey, "apiKey");
911 | // userDefaults.synchronize();
912 | return jsonTextField.stringValue();
913 | } else {
914 | return false;
915 | }
916 | }
917 |
918 | function createImportWindow(context) {
919 | userDefaults = NSUserDefaults.alloc().initWithSuiteName("com.bohemiancoding.sketch.exportSelectedInterface");
920 |
921 | var alert = COSAlertWindow["new"]();
922 | alert.setMessageText("Import Reqfire Project");
923 | alert.addButtonWithTitle("Confirm");
924 | alert.addButtonWithTitle("Cancel");
925 | var viewWidth = 400;
926 | var viewHeight = 150;
927 | var view = NSView.alloc().initWithFrame(NSMakeRect(0, 0, viewWidth, viewHeight));
928 | alert.addAccessoryView(view);
929 |
930 | var linkLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 55, viewWidth - 100, 35));
931 | linkLabel.setStringValue("Your Reqfire Project API key can be found within your project at Reqfire > Export > Sync to Sketch.");
932 | linkLabel.setSelectable(false);
933 | linkLabel.setEditable(false);
934 | linkLabel.setBezeled(false);
935 | linkLabel.setDrawsBackground(false);
936 |
937 | var jsonLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 85, viewWidth - 100, 20));
938 | jsonLabel.setStringValue("Enter your API key");
939 | jsonLabel.setSelectable(false);
940 | jsonLabel.setEditable(false);
941 | jsonLabel.setBezeled(false);
942 | jsonLabel.setDrawsBackground(false);
943 |
944 | view.addSubview(linkLabel);
945 | view.addSubview(jsonLabel);
946 |
947 | jsonTextField = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 105, 300, 20));
948 | view.addSubview(jsonTextField);
949 |
950 | return [alert];
951 | }
952 |
953 | function createExportWindow(context, type) {
954 | // Creates a user defaults object initialized with the defaults for the specified domain name
955 | userDefaults = NSUserDefaults.alloc().initWithSuiteName("com.bohemiancoding.sketch.exportAllInterface");
956 |
957 | var alert = COSAlertWindow["new"]();
958 | if (type === EXPORT_ALL) {
959 | alert.setMessageText("Sync All Artboards");
960 | } else if (type === EXPORT_SELECTED) {
961 | alert.setMessageText("Sync Selected Artboards");
962 | }
963 | alert.addButtonWithTitle("Confirm");
964 | alert.addButtonWithTitle("Cancel");
965 |
966 | var viewWidth = 400;
967 | var viewHeight = 160;
968 | var view = NSView.alloc().initWithFrame(NSMakeRect(0, 0, viewWidth, viewHeight));
969 | alert.addAccessoryView(view);
970 |
971 | var alertLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 55, viewWidth - 100, 50));
972 | var infoLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 95, viewWidth - 100, 35));
973 | var jsonLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 120, viewWidth - 100, 20));
974 |
975 | alertLabel.setStringValue("Note: Any symbols you create that are not sitting inside the symbol artboards generated by this plugin will not be synced up to your Reqfire project.");
976 | alertLabel.setSelectable(false);
977 | alertLabel.setEditable(false);
978 | alertLabel.setBezeled(false);
979 | alertLabel.setDrawsBackground(false);
980 |
981 | infoLabel.setStringValue("Your Reqfire Project API key can be found within your project at Reqfire > Export > Sync to Sketch.");
982 | infoLabel.setSelectable(false);
983 | infoLabel.setEditable(false);
984 | infoLabel.setBezeled(false);
985 | infoLabel.setDrawsBackground(false);
986 |
987 | jsonLabel.setStringValue("Enter your API key");
988 | jsonLabel.setSelectable(false);
989 | jsonLabel.setEditable(false);
990 | jsonLabel.setBezeled(false);
991 | jsonLabel.setDrawsBackground(false);
992 |
993 | view.addSubview(alertLabel);
994 | view.addSubview(infoLabel);
995 | view.addSubview(jsonLabel);
996 |
997 | jsonTextField = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 140, 300, 20));
998 | jsonTextField.setStringValue("");
999 | view.addSubview(jsonTextField);
1000 |
1001 | return [alert];
1002 | }
1003 |
1004 | module.exports = {
1005 | getInputFromUser: getInputFromUser,
1006 | createImportWindow: createImportWindow,
1007 | createExportWindow: createExportWindow
1008 | };
1009 |
1010 | /***/ })
1011 | /******/ ]);
1012 | if (key === 'default' && typeof exports === 'function') {
1013 | exports(context);
1014 | } else {
1015 | exports[key](context);
1016 | }
1017 | }
1018 | that['onRun'] = __skpm_run.bind(this, 'default')
1019 |
--------------------------------------------------------------------------------
/sketch_user_flows.sketchplugin/Contents/Sketch/importReqfireProject.js:
--------------------------------------------------------------------------------
1 | var that = this;
2 | function __skpm_run (key, context) {
3 | that.context = context;
4 |
5 | var exports =
6 | /******/ (function(modules) { // webpackBootstrap
7 | /******/ // The module cache
8 | /******/ var installedModules = {};
9 | /******/
10 | /******/ // The require function
11 | /******/ function __webpack_require__(moduleId) {
12 | /******/
13 | /******/ // Check if module is in cache
14 | /******/ if(installedModules[moduleId]) {
15 | /******/ return installedModules[moduleId].exports;
16 | /******/ }
17 | /******/ // Create a new module (and put it into the cache)
18 | /******/ var module = installedModules[moduleId] = {
19 | /******/ i: moduleId,
20 | /******/ l: false,
21 | /******/ exports: {}
22 | /******/ };
23 | /******/
24 | /******/ // Execute the module function
25 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
26 | /******/
27 | /******/ // Flag the module as loaded
28 | /******/ module.l = true;
29 | /******/
30 | /******/ // Return the exports of the module
31 | /******/ return module.exports;
32 | /******/ }
33 | /******/
34 | /******/
35 | /******/ // expose the modules object (__webpack_modules__)
36 | /******/ __webpack_require__.m = modules;
37 | /******/
38 | /******/ // expose the module cache
39 | /******/ __webpack_require__.c = installedModules;
40 | /******/
41 | /******/ // define getter function for harmony exports
42 | /******/ __webpack_require__.d = function(exports, name, getter) {
43 | /******/ if(!__webpack_require__.o(exports, name)) {
44 | /******/ Object.defineProperty(exports, name, {
45 | /******/ configurable: false,
46 | /******/ enumerable: true,
47 | /******/ get: getter
48 | /******/ });
49 | /******/ }
50 | /******/ };
51 | /******/
52 | /******/ // getDefaultExport function for compatibility with non-harmony modules
53 | /******/ __webpack_require__.n = function(module) {
54 | /******/ var getter = module && module.__esModule ?
55 | /******/ function getDefault() { return module['default']; } :
56 | /******/ function getModuleExports() { return module; };
57 | /******/ __webpack_require__.d(getter, 'a', getter);
58 | /******/ return getter;
59 | /******/ };
60 | /******/
61 | /******/ // Object.prototype.hasOwnProperty.call
62 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
63 | /******/
64 | /******/ // __webpack_public_path__
65 | /******/ __webpack_require__.p = "";
66 | /******/
67 | /******/ // Load entry module and return exports
68 | /******/ return __webpack_require__(__webpack_require__.s = 3);
69 | /******/ })
70 | /************************************************************************/
71 | /******/ ([
72 | /* 0 */
73 | /***/ (function(module, exports) {
74 |
75 | //text colors
76 | var subtitleColor = MSColor.colorWithRed_green_blue_alpha(0.8, 0.8, 0.8, 1);
77 | var groupColor = MSColor.colorWithRed_green_blue_alpha(0.6078431373, 0.6078431373, 0.6078431373, 1);
78 | var flowColor = MSColor.colorWithRed_green_blue_alpha(0.2588235294, 0.2588235294, 0.2588235294, 1);
79 | var versionColor = MSColor.colorWithRed_green_blue_alpha(0, 0, 0, 0.34);
80 | var hintColor = MSColor.colorWithRed_green_blue_alpha(0.8156862745, 0.007843137255, 0.1058823529, 1);
81 |
82 | module.exports = {
83 | subtitleColor: subtitleColor,
84 | groupColor: groupColor,
85 | flowColor: flowColor,
86 | versionColor: versionColor,
87 | hintColor: hintColor
88 | };
89 |
90 | /***/ }),
91 | /* 1 */
92 | /***/ (function(module, exports, __webpack_require__) {
93 |
94 | var insertionSort = __webpack_require__(4).insertionSort;
95 |
96 | // make request to get object data from json url
97 | function getJSONFromURL(url, key) {
98 | var task = NSTask.alloc().init();
99 | task.setLaunchPath("/usr/bin/curl");
100 |
101 | var args = NSMutableArray.alloc().init();
102 | args.addObject("-v");
103 | args.addObject("POST");
104 | args.addObject("-F");
105 | args.addObject("apikey=" + key);
106 | args.addObject(url);
107 | task.setArguments(args);
108 |
109 | var outputPipe = NSPipe.pipe();
110 | task.setStandardOutput(outputPipe);
111 | task.launch();
112 |
113 | var outputData = outputPipe.fileHandleForReading();
114 | var data = outputData.readDataToEndOfFile();
115 | var classNameOfOutput = NSStringFromClass(data["class"]());
116 |
117 | if (classNameOfOutput != "_NSZeroData") {
118 | var res = NSJSONSerialization.JSONObjectWithData_options_error(data, NSJSONReadingMutableContainers, null);
119 |
120 | if (res != null) {
121 | if (res.status == 1) {
122 | //success
123 | //log(res.content);
124 | return res.content;
125 | } else {
126 | return null;
127 | }
128 | }
129 | } else {
130 | return null;
131 | }
132 | }
133 |
134 | //retrive image data from url
135 | function getImageFromURL(url, ingnoreCache) {
136 | var request = ingnoreCache ? NSURLRequest.requestWithURL_cachePolicy_timeoutInterval(NSURL.URLWithString(url), NSURLRequestReloadIgnoringLocalCacheData, 60) : NSURLRequest.requestWithURL(NSURL.URLWithString(url));
137 |
138 | var responsePtr = MOPointer.alloc().init();
139 | var errorPtr = MOPointer.alloc().init();
140 | var data = NSURLConnection.sendSynchronousRequest_returningResponse_error(request, responsePtr, errorPtr);
141 |
142 | if (errorPtr.value() != null) {
143 | return null;
144 | }
145 |
146 | var response = responsePtr.value();
147 |
148 | if (response.statusCode() != 200) {
149 | return null;
150 | }
151 |
152 | var mimeType = response.allHeaderFields()["Content-Type"];
153 |
154 | if (!mimeType || !mimeType.hasPrefix("image/")) {
155 | return null;
156 | }
157 |
158 | return NSImage.alloc().initWithData(data);
159 | }
160 |
161 | function organiseData(object) {
162 | var flattenByKey = function flattenByKey(object, property) {
163 | return Object.keys(object[property]).map(function (key) {
164 | return object[property][key];
165 | });
166 | };
167 | data = {};
168 | data.ifaces = object.ifaces;
169 | data.groups = flattenByKey(object, "groups");
170 |
171 | data.groups = insertionSort(data.groups);
172 | data.groups.forEach(function (group) {
173 | if (group.uc) {
174 | var tempUc = flattenByKey(group, "uc");
175 | group.uc = insertionSort(tempUc);
176 | group.uc.forEach(function (uc) {
177 | uc.flow = flattenByKey(uc, "flow");
178 | // we don't order flows by number (for now)
179 | uc.flow.forEach(function (flow) {
180 | if (flow.step) {
181 | var tempStep = flattenByKey(flow, "step");
182 | flow.step = insertionSort(tempStep);
183 | flow.step.forEach(function (step) {
184 | if (step.object) {
185 | step.object = flattenByKey(step, "object");
186 | step.object = step.object.find(function (obj) {
187 | return obj.object_type == "12";
188 | });
189 | }
190 | });
191 | }
192 | });
193 | });
194 | }
195 | });
196 |
197 | // log(data);
198 | return data;
199 | }
200 |
201 | function logRequest(key, message, isImport) {
202 | var task = NSTask.alloc().init();
203 | task.setLaunchPath("/usr/bin/curl");
204 |
205 | var typeNumber = isImport ? 0 : 1;
206 |
207 | var args = NSMutableArray.alloc().init();
208 | args.addObject("-v");
209 | args.addObject("POST");
210 | args.addObject("-F");
211 | args.addObject("message=" + message);
212 | args.addObject("https://www.reqfire.com/app/project/sketchevent/type/" + typeNumber + "/ext/" + key);
213 | task.setArguments(args);
214 |
215 | var outputPipe = NSPipe.pipe();
216 | task.setStandardOutput(outputPipe);
217 | task.launch();
218 | }
219 |
220 | module.exports = {
221 | getJSONFromURL: getJSONFromURL,
222 | getImageFromURL: getImageFromURL,
223 | organiseData: organiseData,
224 | logRequest: logRequest
225 | };
226 |
227 | /***/ }),
228 | /* 2 */
229 | /***/ (function(module, exports) {
230 |
231 | var IMPORT = "IMPORT";
232 | var EXPORT_ALL = "EXPORT_ALL";
233 | var EXPORT_SELECTED = "EXPORT_SELECTED";
234 |
235 | module.exports = {
236 | IMPORT: IMPORT,
237 | EXPORT_ALL: EXPORT_ALL,
238 | EXPORT_SELECTED: EXPORT_SELECTED
239 | };
240 |
241 | /***/ }),
242 | /* 3 */
243 | /***/ (function(module, exports, __webpack_require__) {
244 |
245 | Object.defineProperty(exports, "__esModule", {
246 | value: true
247 | });
248 |
249 | exports["default"] = function (context) {
250 | coscript.setShouldKeepAround(false);
251 | var sketch = context.api();
252 |
253 | var app = NSApplication.sharedApplication();
254 | var document = sketch.selectedDocument;
255 | var page = document.selectedPage;
256 | var pages = context.document.pages();
257 | var imageUrl = "https://www.reqfire.com/app/images/";
258 | var projectImportURL = "https://www.reqfire.com/app/project/sketchexport/";
259 |
260 | // get file path
261 | var manifestPath = context.plugin.url().URLByAppendingPathComponent("Contents").URLByAppendingPathComponent("Sketch").URLByAppendingPathComponent("manifest.json").path();
262 | // read the .json file content
263 | var manifest = NSJSONSerialization.JSONObjectWithData_options_error(NSData.dataWithContentsOfFile(manifestPath), 0, nil);
264 | var version = manifest.version;
265 |
266 | // default page name when open a new file
267 | var defaultPage = "Page 1";
268 |
269 | // Get the user's project API key. Pass import
270 | var apiKey = getInputFromUser(context, IMPORT);
271 |
272 | if (!apiKey || apiKey.isEqualToString("")) {
273 | // TODO: prompt that they've cancelled.
274 | return null;
275 | }
276 | // Get JSON object
277 | var jsonResponse = getJSONFromURL(projectImportURL, apiKey);
278 |
279 | if (!jsonResponse) {
280 | app.displayDialog_withTitle("Please ensure you are using a valid API key.\nThis can be found within your project at Reqfire > Export > Sync to Sketch.", "Error — No API key was entered");
281 | logRequest(apiKey, "Invalid API Key", true);
282 | return null;
283 | }
284 |
285 | if (!version.isEqualToString(jsonResponse.version)) {
286 | app.displayDialog_withTitle("You can download the latest version from https://github.com/reqfire/sketch-user-flows/", "Error — Plugin version is not up to date");
287 | logRequest(apiKey, "Plugin out of date", true);
288 | log("wrong version");
289 | return null;
290 | }
291 |
292 | // Convert json object
293 | var organisedData = organiseData(jsonResponse);
294 | // Get all symbols (interfaces) in current project
295 | var symbolList = getSymbols(context, pages);
296 | // Current version is the latest
297 |
298 | // Opened file has no symbol page or no symbol
299 | if (!symbolList) {
300 | // Create groups for ifaces
301 | for (var i = 0; i < organisedData.ifaces.length; i++) {
302 | var iName = organisedData.ifaces[i].name;
303 | var iPid = organisedData.ifaces[i].persistent_id;
304 | var iUrl = imageUrl + organisedData.ifaces[i].image;
305 | // create layers for all ifaces in the project
306 | createGroup(iName + " id:" + iPid, iUrl);
307 | }
308 | // get current page after create all the iface layers
309 | var currentPage = context.document.currentPage();
310 | var number = currentPage.layers().length;
311 | var layer;
312 | // create symbol for each layer (group)
313 | for (var i = 0; i < number; i++) {
314 | layer = currentPage.layers()[i];
315 | var layerArray = MSLayerArray.arrayWithLayers([layer]);
316 | var symbolName = currentPage.layers()[i].name();
317 | // create symbol for each layer in the document and send symbols to "Symbol" Page
318 | var newSymbol = MSSymbolCreator.createSymbolFromLayers_withName_onSymbolsPage(layerArray, symbolName, true);
319 | }
320 | } else {
321 | // there are symbols exist already
322 | // create a temporary page to save the layers created for ifaces exist in reqfire project but not on current sketch file
323 | var tempPage = context.document.documentData().addBlankPage();
324 | tempPage.setName("reqfire_temp");
325 | var flag;
326 |
327 | // compare exist symbol's ID with ifaces persistent_id in json object
328 | for (var i = 0; i < organisedData.ifaces.length; i++) {
329 | for (var j = 0; j < symbolList.length; j++) {
330 | var X = symbolList[j].name();
331 | var ID = X.split(" id:")[1];
332 |
333 | if (organisedData.ifaces[i].persistent_id == ID) {
334 | flag = 1; // iface exist in sketch file
335 |
336 | break;
337 | } else {
338 | flag = -1;
339 | }
340 | }
341 |
342 | if (flag == 1) {
343 | // iface exist in sketch file
344 | // update the symbol's name only
345 | updateSymbol(organisedData.ifaces[i].persistent_id, organisedData.ifaces[i].name + " id:" + organisedData.ifaces[i].persistent_id);
346 | } else {
347 | // create new layers for ifaces do not exist in sketch file
348 | var iName = organisedData.ifaces[i].name;
349 | var iUrl = imageUrl + organisedData.ifaces[i].image;
350 | page = document.selectedPage; // update the value of current selected page (reqfire_temp)
351 | createGroup(iName + " id:" + organisedData.ifaces[i].persistent_id, iUrl);
352 | }
353 | }
354 |
355 | var number = tempPage.layers().length;
356 | var layer;
357 |
358 | // create symbol for each layer
359 | for (var i = 0; i < number; i++) {
360 | layer = tempPage.layers()[i];
361 | var layerArray = MSLayerArray.arrayWithLayers([layer]);
362 | var symbolName = tempPage.layers()[i].name();
363 | MSSymbolCreator.createSymbolFromLayers_withName_onSymbolsPage(layerArray, symbolName, true);
364 | }
365 | }
366 | removePage("reqfire_temp");
367 | removePage("User Flows");
368 | removePage("Welcome");
369 | removePage(defaultPage);
370 |
371 | // create a new page to save user flows of the project
372 | var goalPage = context.document.documentData().addBlankPage();
373 | goalPage.setName("User Flows");
374 | organizeSymbol(organisedData); // clean the Symbol page by deleting blank shape
375 | createArtboard(goalPage, organisedData); // create texts and artboards to illustrate all the user flows
376 |
377 | createInfoPage(context, document, version);
378 |
379 | logRequest(apiKey, "Import successful", true);
380 | // complete
381 | return null;
382 |
383 | // create a rectangle shape with image fill
384 | function createGroup(name, url) {
385 | // var FillType = { Solid: 0, Gradient: 1, Pattern: 4, Noise: 5 };
386 | // var PatternFillType = { Tile: 0, Fill: 1, Stretch: 2, Fit: 3 };
387 | var groupShape = page.newShape({
388 | frame: new sketch.Rectangle(200, 200, 1440, 1024),
389 | name: name
390 | });
391 |
392 | // convert shape into a sketch object to enable setting its style
393 | var group = groupShape.sketchObject;
394 | //set shape fills
395 | // var fill = group.style().addStylePartOfType(0);
396 | var imgData = getImageFromURL(url);
397 |
398 | if (imgData) {
399 | var fill = group.style().fills().firstObject();
400 | fill.setFillType(4);
401 | fill.setImage(MSImageData.alloc().initWithImage(imgData));
402 | } else {
403 | log("No image loaded from server for interface");
404 | // bad solution for now, append ! to namd id to check if empty.
405 | group.setName(name + "!");
406 | }
407 | }
408 |
409 | //delete the shape with no fill image on symbol page
410 | function organizeSymbol(obj) {
411 | var symbolMasters = context.document.documentData().allSymbols(); // get all symbols in document
412 |
413 | // further poor implementations. need to find symbols that have been marked with a "!" and
414 | // remove the internals of that symbol
415 | symbolMasters.forEach(function (symbolMaster) {
416 | // search for the corresponding symbol
417 | var nameStr = String(symbolMaster.name().toString());
418 | if (nameStr[nameStr.length - 1] == "!") {
419 | symbolMaster.children()[1].removeFromParent(); // remove the symbol's child element (a shape with no image fill)
420 | symbolMaster.setName(nameStr.substring(0, nameStr.length - 1));
421 | }
422 | });
423 | }
424 |
425 | // create artboard based on groups and use cases
426 | function createArtboard(page, object) {
427 | var setY = 0;
428 |
429 | for (var i = 0; i < object.groups.length; i++) {
430 | var groupName = object.groups[i].name;
431 |
432 | // Group Name Label
433 | var subtitle = MSTextLayer["new"]();
434 | subtitle.setName("Group Name");
435 | subtitle.setStringValue("Group Name");
436 | subtitle.setFontPostscriptName("Helvetica");
437 | subtitle.setFontSize(24);
438 | subtitle.setTextAlignment(0);
439 | subtitle.setTextColor(subtitleColor);
440 | var tFrame = subtitle.frame();
441 | tFrame.setX(0);
442 | tFrame.setY(setY);
443 | page.addLayer(subtitle);
444 |
445 | // Group Name Heading
446 | var text = MSTextLayer["new"]();
447 | text.setName(groupName);
448 | text.setStringValue(groupName);
449 | text.setFontPostscriptName("Helvetica-Bold");
450 | text.setFontSize(64);
451 | text.setTextAlignment(0);
452 | text.setTextColor(groupColor);
453 | var tFrame = text.frame();
454 | tFrame.setX(-3);
455 | tFrame.setY(setY + 29);
456 | page.addLayer(text);
457 |
458 | if (object.groups[i].uc) {
459 | for (var n = 0; n < object.groups[i].uc.length; n++) {
460 | var useCaseName = object.groups[i].uc[n].name;
461 | var flowNo = object.groups[i].uc[n].flow.length;
462 | for (var j = 0; j < flowNo; j++) {
463 | if (object.groups[i].uc[n].flow[j].step) {
464 | var interfaceNo = object.groups[i].uc[n].flow[j].step.length;
465 |
466 | // Flow Name Label
467 |
468 | // Flow Name Heading
469 | var flowHeading = MSTextLayer["new"]();
470 | flowHeading.setName(useCaseName);
471 | flowHeading.setStringValue(useCaseName);
472 | flowHeading.setFontPostscriptName("Helvetica-Bold");
473 | flowHeading.setFontSize(36);
474 | flowHeading.setTextAlignment(0);
475 | flowHeading.setTextColor(flowColor);
476 | var tFrame = flowHeading.frame();
477 | tFrame.setX(70);
478 | tFrame.setY(setY + 29 + 77 + 48); // setY + gap to group heading + group heading height + gap to flow heading
479 | page.addLayer(flowHeading);
480 |
481 | for (var k = 0; k < interfaceNo; k++) {
482 | if (object.groups[i].uc[n].flow[j].step[k].object) {
483 | var interfaceName = object.groups[i].uc[n].flow[j].step[k].object.name;
484 | var interfacePid = object.groups[i].uc[n].flow[j].step[k].object.persistent_id;
485 |
486 | var artboardName = interfaceName;
487 | var artboard = MSArtboardGroup["new"]();
488 | artboard.setName(String.fromCharCode("a".charCodeAt() + k) + ". " + artboardName);
489 | var frame = artboard.frame();
490 | frame.setX(70 + 1540 * k); // offset + (artboard with + gap) * k
491 | frame.setY(setY + 29 + 77 + 48 + 43 + 50); // setY + gap to group heading + group heading height + gap to flow heading + flow heading height + gap to board
492 | frame.setWidth(1440);
493 | frame.setHeight(1024);
494 | var insertSymbol = symbolMasterWithName(
495 | // find the symbol by name that needs to create an instance
496 | context, interfaceName + " id:" + interfacePid);
497 | var symbolInstance = insertSymbol.newSymbolInstance(); // create an symbol instance
498 | page.addLayer(artboard);
499 | artboard.addLayer(symbolInstance);
500 | } else {
501 | var blankText = MSTextLayer["new"]();
502 | blankText.setName("Text");
503 | blankText.setStringValue("No interface linked");
504 | blankText.setFontPostscriptName("Helvetica-Bold");
505 | blankText.setFontSize(64);
506 | blankText.setTextAlignment(1);
507 | var tFrame = blankText.frame();
508 | tFrame.setX(70 + 1540 * k + 1440 / 2 - 573 / 2);
509 | tFrame.setY(setY + 29 + 77 + 48 + 43 + 50 + 1024 / 2 - 77 / 2);
510 | page.addLayer(blankText);
511 | }
512 | }
513 | } else {
514 | // user goal has no step
515 | var flowHeading = MSTextLayer["new"]();
516 | flowHeading.setName(useCaseName + " has no steps");
517 | flowHeading.setStringValue(useCaseName + " has no steps");
518 | flowHeading.setFontPostscriptName("Helvetica-Bold");
519 | flowHeading.setFontSize(36);
520 | flowHeading.setTextAlignment(0);
521 | flowHeading.setTextColor(flowColor);
522 | var tFrame = flowHeading.frame();
523 | tFrame.setX(70);
524 | tFrame.setY(setY + 29 + 77 + 48); // setY + gap to group heading + group heading height + gap to flow heading
525 | page.addLayer(flowHeading);
526 | }
527 | }
528 | setY = setY + 29 + 77 + 48 + 43 + 50 + 1024 + 100;
529 | }
530 | } else {
531 | // group has no use flows
532 | var message = MSTextLayer["new"]();
533 | message.setName(groupName + "has no Flows");
534 | message.setStringValue(groupName + " has no Flows");
535 | message.setFontPostscriptName("Helvetica-Bold");
536 | message.setFontSize(36);
537 | message.setTextAlignment(0);
538 | message.setTextColor(flowColor);
539 | var tFrame = message.frame();
540 | tFrame.setX(70);
541 | tFrame.setY(setY + 29 + 77 + 48);
542 | page.addLayer(message);
543 |
544 | setY = setY + 29 + 77 + 48 + 43 + 50 + 1024 + 100;
545 | }
546 | }
547 | }
548 |
549 | //return symbol by name
550 | function symbolMasterWithName(context, name) {
551 | var loopSymbolMasters = context.document.documentData().allSymbols().objectEnumerator();
552 | var symbolMaster;
553 |
554 | while (symbolMaster = loopSymbolMasters.nextObject()) {
555 | // loop all the symbols
556 | if (symbolMaster.name().isEqualToString(name)) {
557 | return symbolMaster;
558 | }
559 | }
560 | }
561 |
562 | //update symbol
563 | function updateSymbol(id, name) {
564 | var loopSymbolMasters = context.document.documentData().allSymbols().objectEnumerator();
565 | var symbolMaster;
566 |
567 | while (symbolMaster = loopSymbolMasters.nextObject()) {
568 | if (symbolMaster.name().split(" id:")[1] == id) {
569 | symbolMaster.setName(name); // only update symbol name
570 | }
571 | }
572 | }
573 |
574 | // remove unused page by name
575 | function removePage(pageName) {
576 | var pages = context.document.pages();
577 | for (var i = 0; i < pages.count(); i++) {
578 | var page = pages[i];
579 | var name = page.name();
580 | if (name == pageName) {
581 | context.document.documentData().removePage(page);
582 | context.document.pageTreeLayoutDidChange(); //update the page tree
583 | }
584 | }
585 | }
586 |
587 | // get all the symbols on Symbols page
588 | function getSymbols(context, pages) {
589 | for (i = 0; i < pages.count(); i++) {
590 | if (pages[i].name().isEqualToString("Symbols")) {
591 | var layers = pages[i].layers();
592 | return layers;
593 | }
594 | }
595 | }
596 |
597 | function sortNumbers(a, b) {
598 | var aNum = Number(a.number);
599 | var bNum = Number(b.number);
600 | if (aNum < bNum) {
601 | return -1;
602 | }
603 | if (aNum > bNum) {
604 | return 1;
605 | }
606 | return 0;
607 | }
608 | };
609 |
610 | var getJSONFromURL = __webpack_require__(1).getJSONFromURL;
611 | var getImageFromURL = __webpack_require__(1).getImageFromURL;
612 | var organiseData = __webpack_require__(1).organiseData;
613 | var logRequest = __webpack_require__(1).logRequest;
614 | var createInfoPage = __webpack_require__(5).createInfoPage;
615 | var getInputFromUser = __webpack_require__(6).getInputFromUser;
616 | var IMPORT = __webpack_require__(2).IMPORT;
617 |
618 | //text colors
619 | var subtitleColor = __webpack_require__(0).subtitleColor;
620 | var groupColor = __webpack_require__(0).groupColor;
621 | var flowColor = __webpack_require__(0).flowColor;
622 | var versionColor = __webpack_require__(0).versionColor;
623 | var hintColor = __webpack_require__(0).hintColor;
624 |
625 | /***/ }),
626 | /* 4 */
627 | /***/ (function(module, exports) {
628 |
629 | function insertionSort(items) {
630 | var len = items.length,
631 | value,
632 | i,
633 | j;
634 |
635 | for (i = 0; i < len; i++) {
636 | // store the current value because it may shift later
637 | value = items[i];
638 | for (j = i - 1; j > -1 && Number(items[j].number) > Number(value.number); j--) {
639 | items[j + 1] = items[j];
640 | }
641 | items[j + 1] = value;
642 | }
643 | return items;
644 | }
645 |
646 | module.exports = {
647 | insertionSort: insertionSort
648 | };
649 |
650 | /***/ }),
651 | /* 5 */
652 | /***/ (function(module, exports, __webpack_require__) {
653 |
654 | //create Info Page
655 | //text colors
656 | var subtitleColor = __webpack_require__(0).subtitleColor;
657 | var groupColor = __webpack_require__(0).groupColor;
658 | var flowColor = __webpack_require__(0).flowColor;
659 | var versionColor = __webpack_require__(0).versionColor;
660 | var hintColor = __webpack_require__(0).hintColor;
661 |
662 | var getImageFromURL = __webpack_require__(1).getImageFromURL;
663 |
664 | function createInfoPage(context, document, version) {
665 | var infoPage = context.document.documentData().addBlankPage().setName("Welcome");
666 | // infoPage.setName("Welcome");
667 | var page = document.selectedPage;
668 |
669 | var currentPage = context.document.currentPage();
670 |
671 | var welcome_artboard = MSArtboardGroup["new"]();
672 | welcome_artboard.setName("Welcome");
673 | var frame = welcome_artboard.frame();
674 | frame.setX(0);
675 | frame.setY(0);
676 | frame.setWidth(3730);
677 | frame.setHeight(2060);
678 | currentPage.addLayer(welcome_artboard);
679 |
680 | // Reqfire
681 | var welcome_heading_1 = MSTextLayer["new"]();
682 | welcome_heading_1.setName("User Flows");
683 | welcome_heading_1.setStringValue("User Flows");
684 | welcome_heading_1.setFontPostscriptName("Helvetica-Bold");
685 | welcome_heading_1.setFontSize(144);
686 | welcome_heading_1.setTextAlignment(0);
687 | welcome_heading_1.setTextColor(flowColor);
688 | var tFrame = welcome_heading_1.frame();
689 | tFrame.setX(192);
690 | tFrame.setY(300);
691 | welcome_artboard.addLayer(welcome_heading_1);
692 |
693 | // Sketch Plugin
694 | var welcome_heading_2 = MSTextLayer["new"]();
695 | welcome_heading_2.setName("Sketch plugin");
696 | welcome_heading_2.setStringValue("Sketch plugin");
697 | welcome_heading_2.setFontPostscriptName("Helvetica");
698 | welcome_heading_2.setFontSize(144);
699 | welcome_heading_2.setTextAlignment(0);
700 | welcome_heading_2.setTextColor(flowColor);
701 | var tFrame = welcome_heading_2.frame();
702 | tFrame.setX(192);
703 | tFrame.setY(450);
704 | welcome_artboard.addLayer(welcome_heading_2);
705 |
706 | // version
707 | var versionLayer = MSTextLayer["new"]();
708 | versionLayer.setName("Version");
709 | versionLayer.setStringValue(version);
710 | versionLayer.setFontPostscriptName("Helvetica Neue-Light");
711 | versionLayer.setFontSize(36);
712 | versionLayer.setTextAlignment(0);
713 | versionLayer.setTextColor(versionColor);
714 | var tFrame = versionLayer.frame();
715 | tFrame.setX(202);
716 | tFrame.setY(630);
717 | welcome_artboard.addLayer(versionLayer);
718 |
719 | // description_1
720 | var welcome_description_1 = MSTextLayer["new"]();
721 | welcome_description_1.setName("Description_1");
722 | welcome_description_1.setStringValue("A Sketch plug-in for importing/exporting user flows and interfaces from Reqfire.");
723 | welcome_description_1.setFontPostscriptName("Helvetica");
724 | welcome_description_1.setFontSize(36);
725 | welcome_description_1.setTextAlignment(0);
726 | welcome_description_1.setTextColor(flowColor);
727 | welcome_description_1.setTextBehaviour(1); // text is "fixed"
728 | var tFrame = welcome_description_1.frame();
729 | tFrame.setX(200);
730 | tFrame.setY(738);
731 | tFrame.setHeight(86);
732 | tFrame.setWidth(1000);
733 | welcome_artboard.addLayer(welcome_description_1);
734 |
735 | // description_2
736 | var welcome_description_2 = MSTextLayer["new"]();
737 | welcome_description_2.setName("Description_2");
738 | welcome_description_2.setStringValue("User Flows allows the importing of interfaces from Reqfire into a Sketch project, along with the defined Reqfire user flows.");
739 | welcome_description_2.setFontPostscriptName("Helvetica");
740 | welcome_description_2.setFontSize(36);
741 | welcome_description_2.setTextAlignment(0);
742 | welcome_description_2.setTextColor(flowColor);
743 | welcome_description_2.setTextBehaviour(1);
744 | var tFrame = welcome_description_2.frame();
745 | tFrame.setX(200);
746 | tFrame.setY(864);
747 | tFrame.setHeight(86);
748 | tFrame.setWidth(1000);
749 | welcome_artboard.addLayer(welcome_description_2);
750 |
751 | // description_3
752 | var welcome_description_3 = MSTextLayer["new"]();
753 | welcome_description_3.setName("Description_3");
754 | welcome_description_3.setStringValue("Interfaces can be designed in Sketch and then exported back to Reqfire as .png files to be included in the Reqfire project.");
755 | welcome_description_3.setFontPostscriptName("Helvetica");
756 | welcome_description_3.setFontSize(36);
757 | welcome_description_3.setTextAlignment(0);
758 | welcome_description_3.setTextColor(flowColor);
759 | welcome_description_3.setTextBehaviour(1);
760 | var tFrame = welcome_description_3.frame();
761 | tFrame.setX(200);
762 | tFrame.setY(990);
763 | tFrame.setHeight(86);
764 | tFrame.setWidth(1000);
765 | welcome_artboard.addLayer(welcome_description_3);
766 |
767 | // description_4
768 | var welcome_description_4 = MSTextLayer["new"]();
769 | welcome_description_4.setName("Description_4");
770 | welcome_description_4.setStringValue("Subsequent import operations to the same Sketch project will not overwrite existing work, but will add any newly created interfaces and flows.");
771 | welcome_description_4.setFontPostscriptName("Helvetica");
772 | welcome_description_4.setFontSize(36);
773 | welcome_description_4.setTextAlignment(0);
774 | welcome_description_4.setTextColor(flowColor);
775 | welcome_description_4.setTextBehaviour(1);
776 | var tFrame = welcome_description_4.frame();
777 | tFrame.setX(200);
778 | tFrame.setY(1116);
779 | tFrame.setHeight(129);
780 | tFrame.setWidth(1000);
781 | welcome_artboard.addLayer(welcome_description_4);
782 |
783 | // hint
784 | var hint = MSTextLayer["new"]();
785 | hint.setName("Hint");
786 | hint.setStringValue("Create all your designs in the Symbols page\n\nNOTE: Do not change the names of the Symbols");
787 | hint.setFontPostscriptName("Helvetica");
788 | hint.setFontSize(48);
789 | hint.setTextAlignment(2);
790 | hint.setTextColor(hintColor);
791 | hint.setTextBehaviour(1);
792 | var tFrame = hint.frame();
793 | tFrame.setX(1782);
794 | tFrame.setY(552);
795 | tFrame.setHeight(174);
796 | tFrame.setWidth(386);
797 | welcome_artboard.addLayer(hint);
798 |
799 | var FillType = { Solid: 0, Gradient: 1, Pattern: 4, Noise: 5 };
800 | var PatternFillType = { Tile: 0, Fill: 1, Stretch: 2, Fit: 3 };
801 | var introRect = MSShapeGroup.shapeWithRect({
802 | origin: { x: 2172, y: 300 },
803 | size: { width: 1366, height: 1460 }
804 | });
805 | var fill = introRect.style().addStylePartOfType(0);
806 | var image = getImageFromURL("https://www.reqfire.com/images/furniture/welcome_images.png");
807 |
808 | if (image) {
809 | fill.fillType = FillType.Pattern;
810 | fill.patternFillType = PatternFillType.Fit;
811 | fill.image = MSImageData.alloc().initWithImage(image);
812 | } else {
813 | print("Can't load image!");
814 | }
815 |
816 | welcome_artboard.addLayer(introRect);
817 | symbolZoom(context, welcome_artboard);
818 | context.document.pageTreeLayoutDidChange(); //update the page tree
819 | }
820 |
821 | // zooming out current page
822 | function symbolZoom(context, group) {
823 | var targetRect = group.rect();
824 | var padding = 0.025;
825 | targetRect.origin.x -= targetRect.size.width * padding;
826 | targetRect.origin.y -= targetRect.size.height * padding;
827 | targetRect.size.width *= 1 + padding * 2;
828 | targetRect.size.height *= 1 + padding * 2;
829 | var view = getCurrentView(context.document);
830 | view.zoomToFitRect(targetRect);
831 | }
832 |
833 | function getCurrentView(doc) {
834 | if (doc.currentView) {
835 | return doc.currentView();
836 | } else if (doc.contentDrawView) {
837 | return doc.contentDrawView();
838 | }
839 |
840 | log("ERROR: Can not get currentView");
841 | return null;
842 | }
843 |
844 | module.exports = {
845 | createInfoPage: createInfoPage
846 | };
847 |
848 | /***/ }),
849 | /* 6 */
850 | /***/ (function(module, exports, __webpack_require__) {
851 |
852 | var IMPORT = __webpack_require__(2).IMPORT;
853 | var EXPORT_ALL = __webpack_require__(2).EXPORT_ALL;
854 | var EXPORT_SELECTED = __webpack_require__(2).EXPORT_SELECTED;
855 |
856 | function getInputFromUser(context, type) {
857 | var window = type === IMPORT ? createImportWindow(context) : createExportWindow(context, type);
858 | var alert = window[0];
859 | var response = alert.runModal();
860 | if (response == "1000") {
861 | //save user input to user defaults to enable auto filled text field
862 | // userDefaults.setObject_forKey(apiKey, "apiKey");
863 | // userDefaults.synchronize();
864 | return jsonTextField.stringValue();
865 | } else {
866 | return false;
867 | }
868 | }
869 |
870 | function createImportWindow(context) {
871 | userDefaults = NSUserDefaults.alloc().initWithSuiteName("com.bohemiancoding.sketch.exportSelectedInterface");
872 |
873 | var alert = COSAlertWindow["new"]();
874 | alert.setMessageText("Import Reqfire Project");
875 | alert.addButtonWithTitle("Confirm");
876 | alert.addButtonWithTitle("Cancel");
877 | var viewWidth = 400;
878 | var viewHeight = 150;
879 | var view = NSView.alloc().initWithFrame(NSMakeRect(0, 0, viewWidth, viewHeight));
880 | alert.addAccessoryView(view);
881 |
882 | var linkLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 55, viewWidth - 100, 35));
883 | linkLabel.setStringValue("Your Reqfire Project API key can be found within your project at Reqfire > Export > Sync to Sketch.");
884 | linkLabel.setSelectable(false);
885 | linkLabel.setEditable(false);
886 | linkLabel.setBezeled(false);
887 | linkLabel.setDrawsBackground(false);
888 |
889 | var jsonLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 85, viewWidth - 100, 20));
890 | jsonLabel.setStringValue("Enter your API key");
891 | jsonLabel.setSelectable(false);
892 | jsonLabel.setEditable(false);
893 | jsonLabel.setBezeled(false);
894 | jsonLabel.setDrawsBackground(false);
895 |
896 | view.addSubview(linkLabel);
897 | view.addSubview(jsonLabel);
898 |
899 | jsonTextField = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 105, 300, 20));
900 | view.addSubview(jsonTextField);
901 |
902 | return [alert];
903 | }
904 |
905 | function createExportWindow(context, type) {
906 | // Creates a user defaults object initialized with the defaults for the specified domain name
907 | userDefaults = NSUserDefaults.alloc().initWithSuiteName("com.bohemiancoding.sketch.exportAllInterface");
908 |
909 | var alert = COSAlertWindow["new"]();
910 | if (type === EXPORT_ALL) {
911 | alert.setMessageText("Sync All Artboards");
912 | } else if (type === EXPORT_SELECTED) {
913 | alert.setMessageText("Sync Selected Artboards");
914 | }
915 | alert.addButtonWithTitle("Confirm");
916 | alert.addButtonWithTitle("Cancel");
917 |
918 | var viewWidth = 400;
919 | var viewHeight = 160;
920 | var view = NSView.alloc().initWithFrame(NSMakeRect(0, 0, viewWidth, viewHeight));
921 | alert.addAccessoryView(view);
922 |
923 | var alertLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 55, viewWidth - 100, 50));
924 | var infoLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 95, viewWidth - 100, 35));
925 | var jsonLabel = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 120, viewWidth - 100, 20));
926 |
927 | alertLabel.setStringValue("Note: Any symbols you create that are not sitting inside the symbol artboards generated by this plugin will not be synced up to your Reqfire project.");
928 | alertLabel.setSelectable(false);
929 | alertLabel.setEditable(false);
930 | alertLabel.setBezeled(false);
931 | alertLabel.setDrawsBackground(false);
932 |
933 | infoLabel.setStringValue("Your Reqfire Project API key can be found within your project at Reqfire > Export > Sync to Sketch.");
934 | infoLabel.setSelectable(false);
935 | infoLabel.setEditable(false);
936 | infoLabel.setBezeled(false);
937 | infoLabel.setDrawsBackground(false);
938 |
939 | jsonLabel.setStringValue("Enter your API key");
940 | jsonLabel.setSelectable(false);
941 | jsonLabel.setEditable(false);
942 | jsonLabel.setBezeled(false);
943 | jsonLabel.setDrawsBackground(false);
944 |
945 | view.addSubview(alertLabel);
946 | view.addSubview(infoLabel);
947 | view.addSubview(jsonLabel);
948 |
949 | jsonTextField = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 140, 300, 20));
950 | jsonTextField.setStringValue("");
951 | view.addSubview(jsonTextField);
952 |
953 | return [alert];
954 | }
955 |
956 | module.exports = {
957 | getInputFromUser: getInputFromUser,
958 | createImportWindow: createImportWindow,
959 | createExportWindow: createExportWindow
960 | };
961 |
962 | /***/ })
963 | /******/ ]);
964 | if (key === 'default' && typeof exports === 'function') {
965 | exports(context);
966 | } else {
967 | exports[key](context);
968 | }
969 | }
970 | that['onRun'] = __skpm_run.bind(this, 'default')
971 |
--------------------------------------------------------------------------------
/sketch_user_flows.sketchplugin/Contents/Sketch/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Sketch User Flows",
3 | "version": "1.0.1",
4 | "author": "Reqfire",
5 | "compatibleVersion": 3,
6 | "bundleVersion": 1,
7 | "commands": [
8 | {
9 | "name": "Import Reqfire project",
10 | "identifier": "createIFace-identifier",
11 | "handler": "onRun",
12 | "script": "importReqfireProject.js"
13 | },
14 | {
15 | "name": "Sync all symbols to Reqfire",
16 | "identifier": "exportInterface-identifier",
17 | "script": "exportAllInterface.js"
18 | },
19 | {
20 | "name": "Sync selected symbols to Reqfire",
21 | "identifier": "exportSelectedInterface-identifier",
22 | "script": "exportSelectedInterface.js"
23 | }
24 | ],
25 | "menu": {
26 | "title": "Sketch User Flows by Reqfire",
27 | "items": [
28 | "createIFace-identifier",
29 | "exportInterface-identifier",
30 | "exportSelectedInterface-identifier"
31 | ]
32 | },
33 | "disableCocoaScriptPreprocessor": true,
34 | "appcast": "https://raw.githubusercontent.com//master/.appcast.xml"
35 | }
--------------------------------------------------------------------------------