├── Resources
├── Icons.sketch
├── Export Wireframes.png
├── Create Artboard Slice.png
├── Artboard Description Add.png
├── Artboard Layout Settings.png
├── Artboard Title Settings.png
├── Section Title Settings.png
└── Artboard Description Settings.png
├── README.md
├── SNCR.sketchplugin
└── Contents
│ ├── Resources
│ ├── icon.png
│ ├── icons
│ │ ├── layout-update.png
│ │ ├── sections-link.png
│ │ ├── titles-create.png
│ │ ├── annotations-link.png
│ │ ├── descriptions-set.png
│ │ ├── layout-include.png
│ │ ├── layout-preclude.png
│ │ ├── other-hotspots.png
│ │ ├── sections-unlink.png
│ │ ├── sections-update.png
│ │ ├── titles-include.png
│ │ ├── titles-preclude.png
│ │ ├── wireframes-add.png
│ │ ├── annotations-create.png
│ │ ├── annotations-update.png
│ │ ├── descriptions-link.png
│ │ ├── wireframes-export.png
│ │ ├── wireframes-include.png
│ │ ├── annotations-designate.png
│ │ ├── descriptions-unlink.png
│ │ ├── descriptions-update.png
│ │ ├── layout-include-page.png
│ │ ├── layout-preclude-page.png
│ │ └── wireframes-preclude.png
│ └── strings
│ │ └── en.plist
│ └── Sketch
│ ├── Document
│ ├── get-current-id.js
│ ├── set-random-id.js
│ └── set-custom-id.js
│ ├── Groups
│ └── update-content-bounds.js
│ ├── Hotspots
│ ├── toggle-all-in-document.js
│ └── create-over-selected-overrides.js
│ ├── Symbols
│ ├── disable-style-overrides.js
│ ├── replace-override-fills-with-swatch.js
│ └── replace-override-for-instances-of-selection.js
│ ├── Export
│ └── page-to-zeplin.js
│ ├── Text Layers
│ ├── update-text-to-match-style-name.js
│ └── recreate-and-remove-original.js
│ ├── Artboards
│ ├── update-export-settings-for-all-on-page.js
│ ├── insert-mask-in.js
│ ├── create-artboard-instance-of-artboard-symbol.js
│ ├── increment-version-numbers.js
│ └── create-slice-around.js
│ ├── Data
│ ├── script.js
│ └── system.js
│ ├── delegate.js
│ ├── handlers.js
│ ├── Customization
│ ├── run-manifest-audit.js
│ ├── add-feature-link-to-manifest.js
│ └── add-strings-to-manifest.js
│ ├── Text Styles
│ └── convert-to-japanese-style.js
│ ├── library.js
│ ├── manifest.json
│ └── functions.js
└── appcast.xml
/Resources/Icons.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Icons.sketch
--------------------------------------------------------------------------------
/Resources/Export Wireframes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Export Wireframes.png
--------------------------------------------------------------------------------
/Resources/Create Artboard Slice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Create Artboard Slice.png
--------------------------------------------------------------------------------
/Resources/Artboard Description Add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Artboard Description Add.png
--------------------------------------------------------------------------------
/Resources/Artboard Layout Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Artboard Layout Settings.png
--------------------------------------------------------------------------------
/Resources/Artboard Title Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Artboard Title Settings.png
--------------------------------------------------------------------------------
/Resources/Section Title Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Section Title Settings.png
--------------------------------------------------------------------------------
/Resources/Artboard Description Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/Resources/Artboard Description Settings.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # sncr-sketch-plugin
2 | A plugin of Sketch actions which are useful in the workflows of the designers at Synchronoss Technologies Inc.
3 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icon.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/layout-update.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/layout-update.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/sections-link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/sections-link.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/titles-create.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/titles-create.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/annotations-link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/annotations-link.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/descriptions-set.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/descriptions-set.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/layout-include.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/layout-include.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/layout-preclude.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/layout-preclude.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/other-hotspots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/other-hotspots.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/sections-unlink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/sections-unlink.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/sections-update.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/sections-update.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/titles-include.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/titles-include.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/titles-preclude.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/titles-preclude.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/wireframes-add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/wireframes-add.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/annotations-create.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/annotations-create.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/annotations-update.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/annotations-update.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/descriptions-link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/descriptions-link.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/wireframes-export.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/wireframes-export.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/wireframes-include.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/wireframes-include.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/annotations-designate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/annotations-designate.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/descriptions-unlink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/descriptions-unlink.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/descriptions-update.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/descriptions-update.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/layout-include-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/layout-include-page.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/layout-preclude-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/layout-preclude-page.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/icons/wireframes-preclude.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/HEAD/SNCR.sketchplugin/Contents/Resources/icons/wireframes-preclude.png
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Document/get-current-id.js:
--------------------------------------------------------------------------------
1 | @import '../library.js';
2 |
3 | var onRun = function(context) {
4 | sketch.UI.alert('Current Document ID',context.document.documentData().objectID());
5 | }
6 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Document/set-random-id.js:
--------------------------------------------------------------------------------
1 | @import '../library.js';
2 |
3 | var onRun = function(context) {
4 | var oldID = context.document.documentData().objectID();
5 | var newID = context.document.documentData().generateObjectID();
6 |
7 | sketch.UI.alert('Set Random ID','A new document ID, ' + newID + ', has been created for the document (the old ID was ' + oldID + ').\n\nThe document must be saved for the change to take effect.');
8 | }
9 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Groups/update-content-bounds.js:
--------------------------------------------------------------------------------
1 | @import '../library.js';
2 |
3 | const document = sketch.getSelectedDocument();
4 | const selection = document.selectedLayers;
5 |
6 | var onRun = function(context) {
7 | if (!selection.length) {
8 | sketch.UI.alert('Update Group Content Bounds','Nothing is selected.');
9 | return false;
10 | }
11 |
12 | selection.forEach(layer => layer.sketchObject.fixGeometryWithOptions(0));
13 |
14 | sketch.UI.message('The bounds of the selected group(s) have been updated');
15 | }
16 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Document/set-custom-id.js:
--------------------------------------------------------------------------------
1 | @import '../library.js';
2 |
3 | var onRun = function(context) {
4 | var oldID = context.document.documentData().objectID();
5 |
6 | sketch.UI.getInputFromUser(
7 | "Custom ID for Document:",
8 | {
9 | initialValue: '',
10 | },
11 | (err,val) => {
12 | if (err) {
13 | // most likely the user canceled the input
14 | return
15 | }
16 |
17 | if (val) {
18 | context.document.documentData().setObjectID(val);
19 |
20 | sketch.UI.message(oldID + ' has been replaced with ' + val);
21 | }
22 | }
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Hotspots/toggle-all-in-document.js:
--------------------------------------------------------------------------------
1 | @import '../library.js'
2 |
3 | var onRun = function(context) {
4 | var sketch = require('sketch')
5 | var hotspotGroups = []
6 |
7 | sketch.getSelectedDocument().pages.forEach(getHotspotGroups)
8 |
9 | function getHotspotGroups(page) {
10 | page.sketchObject.children().forEach(function(layer){
11 | if (layer.class() == 'MSLayerGroup' && layer.name() == 'Hotspots') hotspotGroups.push(layer)
12 | })
13 | }
14 |
15 | var currentState = hotspotGroups[0].isVisible()
16 |
17 | hotspotGroups.forEach(function(group){
18 | group.setIsVisible(!currentState)
19 | })
20 |
21 | var message = (currentState == 0) ? 'visible 🙉' : 'hidden 🙈'
22 |
23 | sketch.UI.message('All document hotspots are now ' + message)
24 | }
25 |
--------------------------------------------------------------------------------
/appcast.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | SNCR Plugins
5 | http://sparkle-project.org/files/sparkletestcast.xml
6 | A plugin of Sketch actions which are useful in the workflows of the designers at Synchronoss Technologies Inc.
7 | en
8 | -
9 | Version 1.21
10 |
11 |
13 | Improved mask in artboard function, added function to create a hotspot over selected overrides, added function to swap styles to Japanese styles.
14 |
15 | ]]>
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Symbols/disable-style-overrides.js:
--------------------------------------------------------------------------------
1 | @import '../library.js';
2 |
3 | var onRun = function(context) {
4 | var document = sketch.getSelectedDocument();
5 | var page = document.selectedPage;
6 | var count = 0;
7 |
8 | page.sketchObject.symbols().forEach(function(symbol){
9 | var symbol = sketch.fromNative(symbol);
10 |
11 | if (symbol.overrides) {
12 | symbol.overrides.forEach(function(override){
13 | if (override.editable == 1 && override.property == 'layerStyle' || override.property == 'textStyle') {
14 | let overridePoint = override.sketchObject.overridePoint();
15 |
16 | symbol.sketchObject.setOverridePoint_editable(overridePoint,0);
17 |
18 | log(overridePoint + ' on ' + symbol.name + ' has been disabled');
19 |
20 | count++;
21 | }
22 | });
23 | }
24 | });
25 |
26 | document.sketchObject.reloadInspector();
27 |
28 | sketch.UI.message(count + ' style overrides were disabled');
29 | }
30 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Export/page-to-zeplin.js:
--------------------------------------------------------------------------------
1 | @import '../library.js'
2 |
3 | const document = sketch.getSelectedDocument()
4 | const pages = document.pages
5 | const page = document.selectedPage
6 |
7 | const pluginPath = NSHomeDirectory() + '/Library/Application Support/com.bohemiancoding.sketch3/Plugins/Zeplin.sketchplugin'
8 | const pluginURL = NSURL.fileURLWithPath(pluginPath)
9 |
10 | var onRun = function(context) {
11 | if (!NSFileManager.defaultManager().fileExistsAtPath(pluginPath)) {
12 | sketch.UI.alert(`Missing Zeplin Plugin`,`Expected the Zeplin plugin in the following location:\n${pluginPath}`)
13 |
14 | return false
15 | } else {
16 | MSPluginBundle.alloc().initPluginBundleWithURL(pluginURL)
17 | }
18 |
19 | page.layers.forEach(l => {
20 | if (l.sketchObject.class() == 'MSArtboardGroup' || l.sketchObject.class() == 'MSSymbolMaster') {
21 | l.selected = true
22 | }
23 | })
24 |
25 | AppController.sharedInstance().runPluginCommandWithIdentifier_fromBundleAtURL('export',pluginURL)
26 | }
27 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Text Layers/update-text-to-match-style-name.js:
--------------------------------------------------------------------------------
1 | @import '../library.js';
2 |
3 | var onRun = function(context) {
4 | var documentData = context.document.documentData();
5 | var selections = context.selection;
6 |
7 | if (!selections.length) {
8 | sketch.UI.alert('Update Text to Match Style Name…','Select at least one text layer.');
9 | return false;
10 | }
11 |
12 | selections.forEach(updateLayerText);
13 |
14 | function updateLayerText(textLayer) {
15 | var textLayerSharedStyleID = textLayer.sharedStyleID();
16 | var textLayerStyle = documentData.textStyleWithID(textLayerSharedStyleID);
17 | var textLayerStyleName = textLayerStyle.name();
18 |
19 | textLayer.setStringValue(textLayerStyleName);
20 | textLayer.setName(textLayerStyleName);
21 | }
22 |
23 | var message = 'The text layer name has been updated';
24 |
25 | if (selections.length > 1) {
26 | message = selections.length + ' text layer names have been updated';
27 | }
28 |
29 | sketch.UI.message(message);
30 | }
31 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Artboards/update-export-settings-for-all-on-page.js:
--------------------------------------------------------------------------------
1 | @import '../library.js';
2 |
3 | var onRun = function(context) {
4 | var document = context.document;
5 | var count = 0;
6 |
7 | document.currentPage().artboards().forEach(updateExportSettings);
8 |
9 | function updateExportSettings(artboard) {
10 | var updated = false;
11 |
12 | if (artboard.exportOptions().layerOptions() != 2) {
13 | artboard.exportOptions().setLayerOptions(2);
14 |
15 | updated = true;
16 | }
17 |
18 | if (!artboard.exportOptions().exportFormats().length) {
19 | var format = artboard.exportOptions().addExportFormat();
20 |
21 | format.setScale(2);
22 | format.setFileFormat('png');
23 |
24 | updated = true;
25 | }
26 |
27 | if (updated) {
28 | log(`Export settings were updated for ${artboard.name()}`);
29 |
30 | count++;
31 | }
32 | }
33 |
34 | document.reloadInspector();
35 |
36 | var message = 'No artboards need updating';
37 |
38 | if (count !== 0) {
39 | message = count + ' artboards were updated';
40 | }
41 |
42 | sketch.UI.message(message);
43 | }
44 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Artboards/insert-mask-in.js:
--------------------------------------------------------------------------------
1 | @import '../library.js'
2 |
3 | var document = sketch.getSelectedDocument()
4 | var selections = document.selectedLayers.layers
5 |
6 | var onRun = function(context) {
7 | if (!selections.length) {
8 | sketch.UI.alert('Insert Mask in Selected Artboards','Select at least one artboard (or symbol).')
9 | return false
10 | }
11 |
12 | selections.forEach(selection => {
13 | if (selection.type == 'Artboard' || selection.type == 'SymbolMaster') {
14 | let mask = new sketch.ShapePath({
15 | frame: {
16 | x: 0,
17 | y: 0,
18 | width: selection.frame.width,
19 | height: selection.frame.height
20 | },
21 | name: 'Mask',
22 | parent: selection
23 | })
24 |
25 | mask.moveToBack()
26 |
27 | mask.sketchObject.setHasFixedTop(1)
28 | mask.sketchObject.setHasFixedRight(1)
29 | mask.sketchObject.setHasFixedBottom(1)
30 | mask.sketchObject.setHasFixedLeft(1)
31 | mask.sketchObject.setHasClippingMask(1)
32 | }
33 | })
34 |
35 | var reselect = selections.slice(0)
36 |
37 | document.selectedLayers.clear()
38 |
39 | reselect.forEach(selection => {
40 | selection.sketchObject.select_byExtendingSelection(1,1)
41 | })
42 | }
43 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Symbols/replace-override-fills-with-swatch.js:
--------------------------------------------------------------------------------
1 | @import '../library.js'
2 | @import '../delegate.js'
3 |
4 | const document = sketch.getSelectedDocument()
5 | const selections = document.selectedLayers
6 | const swatches = document.swatches
7 |
8 | var onRun = function(context) {
9 | if (selections.length == 0) {
10 | sketch.UI.alert('Replace Override Fills with Swatch','Select at least one symbol instance')
11 |
12 | return
13 | }
14 |
15 | sketch.UI.getInputFromUser('Select the swatch to override with…',
16 | {
17 | type: sketch.UI.INPUT_TYPE.selection,
18 | possibleValues: swatches.map(s => s.name),
19 | },
20 | (err,val) => {
21 | if (err) {
22 | // User most likely canceled input
23 | return
24 | }
25 |
26 | let swatch = swatches.find(s => s.name == val)
27 | let swatchReference = swatch.sketchObject.makeReferencingColor()
28 |
29 | let updateCount = 0
30 |
31 | selections.layers.forEach(selection => {
32 | if (selection.overrides) {
33 | let uneditedOverrides = selection.overrides.filter(o => o.property == 'fillColor' && o.isDefault == true)
34 | //let editedOverrides = selection.overrides.filter(o => o.property == 'fillColor' && o.isDefault == false)
35 |
36 | uneditedOverrides.forEach(o => {
37 | o.value = swatchReference
38 | updateCount++
39 | })
40 | }
41 | })
42 |
43 | sketch.UI.message(`${updateCount} overrides have been updated`)
44 | }
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Text Layers/recreate-and-remove-original.js:
--------------------------------------------------------------------------------
1 | @import '../library.js';
2 |
3 | var onRun = function(context) {
4 | var documentData = context.document.documentData();
5 |
6 | var selection = context.selection;
7 |
8 | if (!selection.length) {
9 | sketch.UI.alert('Recreate and Remove Original…','Select at least one text layer.');
10 | return false;
11 | }
12 |
13 | selection.forEach(replaceTextLayer);
14 |
15 | function replaceTextLayer(textLayer) {
16 | var textLayerParent = textLayer.parentObject();
17 | var textLayerIndex = textLayerParent.indexOfLayer(textLayer);
18 | var textLayerString = textLayer.stringValue();
19 | var textLayerSharedStyleID = textLayer.sharedStyleID();
20 | var textLayerStyle = (textLayerSharedStyleID) ? documentData.textStyleWithID(textLayerSharedStyleID) : textLayer.style();
21 |
22 | var newTextLayer = MSTextLayer.new();
23 |
24 | newTextLayer.setStringValue(textLayerString);
25 | newTextLayer.setName(textLayerString);
26 | newTextLayer.setFrame(textLayer.frame());
27 |
28 | if (textLayerSharedStyleID) {
29 | newTextLayer.setSharedStyle(textLayerStyle);
30 | } else {
31 | newTextLayer.setStyle(textLayerStyle);
32 | }
33 |
34 | textLayerParent.insertLayer_atIndex(newTextLayer,textLayerIndex);
35 | textLayer.removeFromParent();
36 | }
37 |
38 | var message = 'The text layer has been recreated';
39 |
40 | if (selection.length > 1) {
41 | message = selection.length + ' text layers have been recreated';
42 | }
43 |
44 | sketch.UI.message(message);
45 | }
46 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Artboards/create-artboard-instance-of-artboard-symbol.js:
--------------------------------------------------------------------------------
1 | @import '../library.js';
2 |
3 | var document = sketch.getSelectedDocument();
4 | var selections = document.selectedLayers.layers;
5 | var page = document.selectedPage;
6 |
7 | var onRun = function(context) {
8 | if (!selections.length || selections.length > 1) {
9 | sketch.UI.alert('Create Artboard Instance of Artboard Symbol','Select one artboard symbol.');
10 | return false;
11 | }
12 |
13 | var selection = selections[0];
14 |
15 | if (selection.type != 'SymbolMaster') {
16 | sketch.UI.alert('Create Artboard Instance of Artboard Symbol','Artboard is not a symbol.');
17 | return false;
18 | }
19 |
20 | var artboard = new sketch.Artboard({
21 | name: selection.name,
22 | frame: new sketch.Rectangle(
23 | selection.frame.x + selection.frame.width + 100,
24 | selection.frame.y,
25 | selection.frame.width,
26 | selection.frame.height
27 | )
28 | });
29 |
30 | artboard = artboard.sketchObject;
31 |
32 | page.sketchObject.insertLayer_afterLayer(artboard,selection.sketchObject);
33 |
34 | var instance = selection.sketchObject.newSymbolInstance();
35 | instance.setHasFixedTop(1);
36 | instance.setHasFixedRight(1);
37 | instance.setHasFixedBottom(1);
38 | instance.setHasFixedLeft(1);
39 |
40 | artboard.addLayer(instance);
41 | artboard.setHasBackgroundColor(1);
42 | artboard.setResizesContent(1);
43 | artboard.exportOptions().setLayerOptions(2);
44 |
45 | var exportFormat = artboard.exportOptions().addExportFormat();
46 | exportFormat.setScale(2);
47 | exportFormat.setFileFormat('png');
48 |
49 | artboard.select_byExtendingSelection(1,0);
50 |
51 | sketch.UI.message('Instance of artboard created');
52 | }
53 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Hotspots/create-over-selected-overrides.js:
--------------------------------------------------------------------------------
1 | @import '../library.js'
2 |
3 | var onRun = function(context) {
4 | const sketch = require('sketch')
5 | const document = sketch.getSelectedDocument()
6 | const selections = document.selectedLayers
7 | const selection = selections.layers[0]
8 |
9 | if (!selections.length || selections.length > 1 || selection.type !== 'SymbolInstance') {
10 | sketch.UI.alert('Create Hotspot Over Selected Overrides','Select one or many overrides within a single symbol instance.')
11 | return
12 | }
13 |
14 | let selectedOverrides = selection.overrides.filter(o => o.selected)
15 |
16 | if (!selectedOverrides) {
17 | sketch.UI.alert('Create Hotspot Over Selected Overrides','Select one or many overrides within a single symbol instance.')
18 | return
19 | }
20 |
21 | let parentArtboard = selection.getParentArtboard()
22 | let hotspotGroup = parentArtboard.layers.find(l => sketch.Settings.layerSettingForKey(l,'hotspotGroup') == true)
23 | let hotspotCount = 0
24 |
25 | if (!hotspotGroup) {
26 | hotspotGroup = new sketch.Group({
27 | name: 'Hotspots',
28 | locked: true,
29 | parent: parentArtboard
30 | })
31 |
32 | sketch.Settings.setLayerSettingForKey(hotspotGroup,'hotspotGroup',true)
33 | }
34 |
35 | selectedOverrides.forEach(o => {
36 | new sketch.HotSpot({
37 | name: 'Hotspot',
38 | frame: new sketch.Rectangle(
39 | o.getFrame().x - hotspotGroup.frame.x,
40 | o.getFrame().y - hotspotGroup.frame.y,
41 | o.getFrame().width,
42 | o.getFrame().height
43 | ),
44 | parent: hotspotGroup
45 | })
46 |
47 | hotspotCount++
48 | })
49 |
50 | hotspotGroup.adjustToFit()
51 |
52 | sketch.UI.message(`${hotspotCount} hotspots created`)
53 | }
54 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Artboards/increment-version-numbers.js:
--------------------------------------------------------------------------------
1 | @import '../library.js'
2 |
3 | const document = sketch.getSelectedDocument()
4 | const page = document.selectedPage
5 | const selections = document.selectedLayers
6 |
7 | var onRun = function(context) {
8 | var positions = ['First','Second','Third','Fourth']
9 |
10 | var alertWindow = createAlertWindow(context,'Increment Version Numbers')
11 |
12 | var positionLabel = createAlertLabel('Number position:',NSMakeRect(0,0,300,14))
13 |
14 | alertWindow.addAccessoryView(positionLabel)
15 |
16 | var positionSelect = createAlertPopupButton(positions,1,NSMakeRect(0,0,120,28))
17 |
18 | alertWindow.addAccessoryView(positionSelect)
19 |
20 | var amountLabel = createAlertLabel('Increment amount (e.g. 1, 2 -1):',NSMakeRect(0,0,300,14))
21 |
22 | alertWindow.addAccessoryView(amountLabel)
23 |
24 | var amountValue = createAlertField('',NSMakeRect(0,0,60,23))
25 |
26 | alertWindow.addAccessoryView(amountValue)
27 |
28 | var submit = alertWindow.addButtonWithTitle('OK')
29 | var cancel = alertWindow.addButtonWithTitle('Cancel')
30 |
31 | // Set key order
32 | setKeyOrder(alertWindow.alert(),[
33 | positionSelect,
34 | amountValue,
35 | submit
36 | ])
37 |
38 | var response = alertWindow.runModal()
39 |
40 | if (response == 1000) {
41 | var amount = Number(amountValue.stringValue())
42 |
43 | if (Number.isInteger(amount) && amount !== 0) {
44 | let layers = selections.layers || page.layers
45 |
46 | layers.forEach(l => {
47 | var types = ['Artboard','SymbolMaster']
48 |
49 | if (types.includes(l.type)) {
50 | let version = l.name.substr(0,l.name.indexOf(' '))
51 | let versionParts = version.split('.')
52 | let remainder = l.name.substr(l.name.indexOf(' '))
53 |
54 | let position = positionSelect.indexOfSelectedItem()
55 |
56 | versionParts[position] = Number(versionParts[position]) + amount
57 |
58 | l.name = versionParts.join('.') + remainder
59 | }
60 | })
61 |
62 | let scope = (selections.layers) ? 'Selected' : 'All'
63 |
64 | sketch.UI.message(`${scope} artboards incremented by ${amount}`)
65 | } else {
66 | sketch.UI.alert('Increment Version Numbers for Artboards on Page','Please provide a valid number to increment by.')
67 | }
68 | } else return false
69 | }
70 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Data/script.js:
--------------------------------------------------------------------------------
1 | @import 'system.js';
2 |
3 | const sketch = require("sketch")
4 | const util = require("util")
5 |
6 | var DataSupplier = require("sketch/data-supplier");
7 |
8 | var onStartup = function() {
9 | DataSupplier.registerDataSupplier("public.text", "Text From File", "SupplyTextFromFile");
10 | DataSupplier.registerDataSupplier("public.image", "Image From Folder", "SupplyImageFromFolder");
11 | DataSupplier.registerDataSupplier("public.text", "Random Text From File", "SupplyRandomTextFromFile");
12 | DataSupplier.registerDataSupplier("public.image", "Random Image From Folder", "SupplyRandomImageFromFolder");
13 | };
14 |
15 | var onShutdown = function() {
16 | DataSupplier.deregisterDataSuppliers();
17 | };
18 |
19 | var onSupplyTextFromFile = function(context) {
20 | var texts = System.textsFromChooseFile();
21 | if (texts.length > 0) {
22 | supplyOrderedData(context, texts);
23 | }
24 | };
25 |
26 | var onSupplyImageFromFolder = function(context) {
27 | var images = System.imagesFromChooseFolder();
28 | if (images.length > 0) {
29 | supplyOrderedData(context, images);
30 | }
31 | };
32 |
33 | var onSupplyRandomTextFromFile = function(context) {
34 | var texts = System.textsFromChooseFile();
35 | if (texts.length > 0) {
36 | supplyRandomData(context, texts);
37 | }
38 | };
39 |
40 | var onSupplyRandomImageFromFolder = function(context) {
41 | var images = System.imagesFromChooseFolder();
42 | if (images.length > 0) {
43 | supplyRandomData(context, images);
44 | }
45 | };
46 |
47 | function supplyOrderedData(context, data) {
48 | for (var i = 0; i < context.data.requestedCount; i++) {
49 | var dataIndex;
50 | if (context.data.isSymbolInstanceOverride == 1) {
51 | var selection = NSDocumentController.sharedDocumentController().currentDocument().selectedLayers().layers();
52 | dataIndex = selection.indexOfObject(context.data.items.objectAtIndex(i).symbolInstance())
53 | } else {
54 | dataIndex = i;
55 | }
56 | DataSupplier.supplyDataAtIndex(context.data.key, data[dataIndex % data.length], i);
57 | }
58 | }
59 |
60 | function supplyRandomData(context, data) {
61 | for (var i = 0; i < context.data.requestedCount; i++) {
62 | DataSupplier.supplyDataAtIndex(context.data.key, data[Math.floor(Math.random() * data.length)], i);
63 | }
64 | }
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/delegate.js:
--------------------------------------------------------------------------------
1 | //
2 | // MochaJSDelegate.js
3 | // MochaJSDelegate
4 | //
5 | // Created by Matt Curtis
6 | // Copyright (c) 2015. All rights reserved.
7 | //
8 |
9 | var MochaJSDelegate = function(selectorHandlerDict){
10 | var uniqueClassName = "MochaJSDelegate_DynamicClass_" + NSUUID.UUID().UUIDString();
11 |
12 | var delegateClassDesc = MOClassDescription.allocateDescriptionForClassWithName_superclass_(uniqueClassName, NSObject);
13 |
14 | delegateClassDesc.registerClass();
15 |
16 | // Handler storage
17 |
18 | var handlers = {};
19 |
20 | // Define interface
21 |
22 | this.setHandlerForSelector = function(selectorString, func){
23 | var handlerHasBeenSet = (selectorString in handlers);
24 | var selector = NSSelectorFromString(selectorString);
25 |
26 | handlers[selectorString] = func;
27 |
28 | if(!handlerHasBeenSet){
29 | /*
30 | For some reason, Mocha acts weird about arguments:
31 | https://github.com/logancollins/Mocha/issues/28
32 |
33 | We have to basically create a dynamic handler with a likewise dynamic number of predefined arguments.
34 | */
35 |
36 | var dynamicHandler = function(){
37 | var functionToCall = handlers[selectorString];
38 |
39 | if(!functionToCall) return;
40 |
41 | return functionToCall.apply(delegateClassDesc, arguments);
42 | };
43 |
44 | var args = [], regex = /:/g;
45 | while(match = regex.exec(selectorString)) args.push("arg"+args.length);
46 |
47 | dynamicFunction = eval("(function("+args.join(",")+"){ return dynamicHandler.apply(this, arguments); })");
48 |
49 | delegateClassDesc.addInstanceMethodWithSelector_function_(selector, dynamicFunction);
50 | }
51 | };
52 |
53 | this.removeHandlerForSelector = function(selectorString){
54 | delete handlers[selectorString];
55 | };
56 |
57 | this.getHandlerForSelector = function(selectorString){
58 | return handlers[selectorString];
59 | };
60 |
61 | this.getAllHandlers = function(){
62 | return handlers;
63 | };
64 |
65 | this.getClass = function(){
66 | return NSClassFromString(uniqueClassName);
67 | };
68 |
69 | this.getClassInstance = function(){
70 | return NSClassFromString(uniqueClassName).new();
71 | };
72 |
73 | // Conveience
74 |
75 | if(typeof selectorHandlerDict == "object"){
76 | for(var selectorString in selectorHandlerDict){
77 | this.setHandlerForSelector(selectorString, selectorHandlerDict[selectorString]);
78 | }
79 | }
80 | };
81 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Data/system.js:
--------------------------------------------------------------------------------
1 | var System = {};
2 |
3 | System.chooseFile = function() {
4 | var panel = NSOpenPanel.openPanel();
5 | panel.setCanChooseDirectories(false);
6 | panel.setCanChooseFiles(true);
7 | panel.setCanCreateDirectories(false);
8 | panel.setAllowedFileTypes(["txt"]);
9 | if (panel.runModal() == NSOKButton) {
10 | return panel.URL().path();
11 | }
12 | };
13 |
14 | System.chooseFolder = function(setCanCreateDirectories) {
15 | var panel = NSOpenPanel.openPanel();
16 | panel.setCanChooseDirectories(true);
17 | panel.setCanChooseFiles(false);
18 | if (setCanCreateDirectories == true) {
19 | panel.setCanCreateDirectories(true);
20 | }
21 | if (panel.runModal() == NSOKButton) {
22 | return panel.URL().path();
23 | }
24 | };
25 |
26 | System.savePanel = function(defaultName) {
27 | var panel = NSSavePanel.savePanel();
28 | if (defaultName) {
29 | panel.setNameFieldStringValue(defaultName);
30 | }
31 | panel.setCanCreateDirectories(true);
32 | if (panel.runModal() == NSOKButton) {
33 | return panel.URL().path();
34 | }
35 | };
36 |
37 | System.textsFromFile = function(path) {
38 | var contents = NSString.stringWithContentsOfFile_encoding_error(path, NSUTF8StringEncoding, nil);
39 | var data = contents.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet());
40 | var texts = [];
41 | var loopData = data.objectEnumerator();
42 | var item;
43 | while (item = loopData.nextObject()) {
44 | if (item.length() > 0) {
45 | texts.push(String(item));
46 | }
47 | }
48 | return texts;
49 | };
50 |
51 | System.imagesFromFolder = function(path) {
52 | var images = [];
53 | var supportFormats = ["png", "jpg", "jpeg", "tif", "tiff", "gif", "webp", "bmp"];
54 | var fileManager = NSFileManager.defaultManager();
55 | var fileList = fileManager.contentsOfDirectoryAtPath_error(path, nil);
56 | fileList = fileList.sortedArrayUsingSelector("localizedStandardCompare:");
57 | fileList.forEach(function(file) {
58 | if (supportFormats.indexOf(String(file.pathExtension().lowercaseString())) != -1) {
59 | images.push(path + "/" + file);
60 | }
61 | });
62 | return images;
63 | };
64 |
65 | System.textsFromChooseFile = function() {
66 | var textFile = System.chooseFile();
67 | if (textFile == nil) {
68 | return [];
69 | } else {
70 | return System.textsFromFile(textFile);
71 | }
72 | }
73 |
74 | System.imagesFromChooseFolder = function() {
75 | var imageFolder = System.chooseFolder();
76 | if (imageFolder == nil) {
77 | return [];
78 | } else {
79 | return System.imagesFromFolder(imageFolder);
80 | }
81 | }
82 |
83 | System.writeStringToFile = function(content, filePath) {
84 | NSString.stringWithString(content).writeToFile_atomically_encoding_error_(
85 | filePath, true, NSUTF8StringEncoding, nil
86 | );
87 | };
88 |
89 | System.showInFinder = function(filePath) {
90 | NSWorkspace.sharedWorkspace().selectFile_inFileViewerRootedAtPath(filePath, nil);
91 | };
92 |
93 | System.mkdir = function(filePath) {
94 | return NSFileManager.defaultManager().createDirectoryAtPath_withIntermediateDirectories_attributes_error_(
95 | filePath, true, nil, nil
96 | );
97 | };
98 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/handlers.js:
--------------------------------------------------------------------------------
1 | @import 'functions.js';
2 |
3 | var sectionsLink = function(context) {
4 | sncr.init(context,"sections-link");
5 | }
6 |
7 | var sectionsUnlink = function(context) {
8 | sncr.init(context,"sections-unlink");
9 | }
10 |
11 | var sectionsUpdate = function(context) {
12 | sncr.init(context,"sections-update");
13 | }
14 |
15 | var sectionsSettings = function(context) {
16 | sncr.init(context,"sections-settings");
17 | }
18 |
19 | var titlesCreate = function(context) {
20 | sncr.init(context,"titles-create");
21 | }
22 |
23 | var titlesInclude = function(context) {
24 | sncr.init(context,"titles-include");
25 | }
26 |
27 | var titlesPreclude = function(context) {
28 | sncr.init(context,"titles-preclude");
29 | }
30 |
31 | var titlesSettings = function(context) {
32 | sncr.init(context,"titles-settings");
33 | }
34 |
35 | var descriptionsSet = function(context) {
36 | sncr.init(context,"descriptions-set");
37 | }
38 |
39 | var descriptionsLink = function(context) {
40 | sncr.init(context,"descriptions-link");
41 | }
42 |
43 | var descriptionsUnlink = function(context) {
44 | sncr.init(context,"descriptions-unlink");
45 | }
46 |
47 | var descriptionsUpdate = function(context) {
48 | sncr.init(context,"descriptions-update");
49 | }
50 |
51 | var descriptionsSettings = function(context) {
52 | sncr.init(context,"descriptions-settings");
53 | }
54 |
55 | var layoutUpdate = function(context) {
56 | sncr.init(context,"layout-update");
57 | }
58 |
59 | var layoutIncludeSelected = function(context) {
60 | sncr.init(context,"layout-include-selected");
61 | }
62 |
63 | var layoutPrecludeSelected = function(context) {
64 | sncr.init(context,"layout-preclude-selected");
65 | }
66 |
67 | var layoutIncludePage = function(context) {
68 | sncr.init(context,"layout-include-page");
69 | }
70 |
71 | var layoutPrecludePage = function(context) {
72 | sncr.init(context,"layout-preclude-page");
73 | }
74 |
75 | var layoutSettings = function(context) {
76 | sncr.init(context,"layout-settings");
77 | }
78 |
79 | var annotationsCreate = function(context) {
80 | sncr.init(context,"annotations-create");
81 | }
82 |
83 | var annotationsDesignate = function(context) {
84 | sncr.init(context,"annotations-designate");
85 | }
86 |
87 | var annotationsLink = function(context) {
88 | sncr.init(context,"annotations-link");
89 | }
90 |
91 | var annotationsPosition = function(context) {
92 | sncr.init(context,"annotations-position");
93 | }
94 |
95 | var annotationsUpdate = function(context) {
96 | sncr.init(context,"annotations-update");
97 | }
98 |
99 | var annotationsSettings = function(context) {
100 | sncr.init(context,"annotations-settings");
101 | }
102 |
103 | var wireframesAdd = function(context) {
104 | sncr.init(context,"wireframes-add");
105 | }
106 |
107 | var wireframesInclude = function(context) {
108 | sncr.init(context,"wireframes-include");
109 | }
110 |
111 | var wireframesPreclude = function(context) {
112 | sncr.init(context,"wireframes-preclude");
113 | }
114 |
115 | var wireframesExport = function(context) {
116 | sncr.init(context,"wireframes-export");
117 | }
118 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Customization/run-manifest-audit.js:
--------------------------------------------------------------------------------
1 | var onRun = function(context) {
2 | const sketch = require('sketch')
3 | const document = sketch.getSelectedDocument()
4 |
5 | const fileManager = NSFileManager.defaultManager()
6 |
7 | let manifestName = 'manifest.json'
8 | let manifestPath = document.path.replace(document.sketchObject.displayName(),manifestName)
9 | manifestPath = manifestPath.replaceAll('%20',' ')
10 | let manifestData = {}
11 |
12 | if (fileManager.fileExistsAtPath(manifestPath)) {
13 | manifestData = NSString.stringWithContentsOfFile_encoding_error(manifestPath,NSUTF8StringEncoding,null)
14 | manifestData = JSON.parse(manifestData)
15 | } else {
16 | sketch.UI.alert(`Audit Manifest`,`A manifest could not be found for the current document.`)
17 |
18 | return
19 | }
20 |
21 | let missingStringObjects = []
22 | let missingStringOverrides = []
23 | let updateCount = 0
24 |
25 | if (manifestData.strings) {
26 | manifestData.strings.forEach(string => {
27 | let objectIDs = string.stringObjectID.split('+')
28 |
29 | let layer = document.getLayerWithID(objectIDs[0])
30 |
31 | if (!layer) missingStringObjects.push(string)
32 |
33 | if (layer && layer.type == 'SymbolInstance') {
34 | let override = layer.overrides.find(o => o.id == objectIDs[1])
35 |
36 | if (!override) {
37 | let overrideList = []
38 | let overrideProp = string.stringObjectID.split('_')[1]
39 | let overrides = layer.overrides.filter(o => o.editable && o.property == overrideProp)
40 |
41 | overrides.forEach(o => overrideList.push(o.id))
42 |
43 | let parent = layer.getParentArtboard()
44 |
45 | document.centerOnLayer(parent)
46 |
47 | sketch.UI.getInputFromUser(
48 | `The symbol instance ${layer.name} has an orphaned manifest string. Select a new string for "${string.stringExample}" (${objectIDs[1]}) from the list below.`,
49 | {
50 | type: sketch.UI.INPUT_TYPE.selection,
51 | possibleValues: overrideList
52 | },
53 | (err,val) => {
54 | if (err) {
55 | missingStringOverrides.push(string)
56 |
57 | return
58 | }
59 |
60 | if (val) {
61 | string.stringObjectID = `${layer.id}+${val}`
62 |
63 | updateCount++
64 |
65 | return
66 | }
67 | }
68 | )
69 | }
70 | }
71 | })
72 | }
73 |
74 | if (updateCount) {
75 | manifestData = JSON.stringify(manifestData)
76 | manifestData = NSString.stringWithFormat('%@',manifestData)
77 | manifestData.dataUsingEncoding_(NSUTF8StringEncoding).writeToFile_atomically_(manifestPath,true)
78 | }
79 |
80 | let messageStringObjects = (missingStringObjects) ? `${missingStringObjects.length} string layers` : ``
81 | let messageStringOverrides = (missingStringOverrides) ? `, ${missingStringOverrides.length} string layer overrides` : ``
82 | let messageStringUpdates = (updateCount) ? `, ${updateCount} strings updated` : ``
83 |
84 | sketch.UI.message(`Manifest Audit: missing ${messageStringObjects}${messageStringOverrides}${messageStringUpdates}`)
85 |
86 | console.log(`Manifest Audit:`,`missingStringObjects`,missingStringObjects)
87 | console.log(`Manifest Audit:`,`missingStringOverrides`,missingStringOverrides)
88 | }
89 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Symbols/replace-override-for-instances-of-selection.js:
--------------------------------------------------------------------------------
1 | @import '../library.js';
2 | @import '../delegate.js';
3 |
4 | const document = sketch.getSelectedDocument();
5 | const selection = document.selectedLayers;
6 |
7 | var onRun = function(context) {
8 | if (selection.length != 1) {
9 | sketch.UI.alert('Replace Text Override for Instances of Selected','Select one symbol instance');
10 | return false;
11 | }
12 |
13 | var instance = selection.layers[0];
14 | var instanceOverrides = getInstanceOverrides(instance);
15 | var masterInstances = instance.master.getAllInstances();
16 |
17 | var alert = NSAlert.alloc().init();
18 |
19 | alert.setMessageText('Replace Text Override for Instances of Selected');
20 | alert.icon = getPluginAlertIcon();
21 |
22 | var alertContent = NSView.alloc().init();
23 |
24 | alertContent.setFlipped(true);
25 |
26 | var instanceLabel = createAlertLabelBold(instance.master.name + ' has ' + masterInstances.length + ' instance(s)',NSMakeRect(0,0,alertWidth,alertLabelHeight));
27 |
28 | alertContent.addSubview(instanceLabel);
29 |
30 | var overrideSelectLabel = createAlertLabel('Text override to replace:',NSMakeRect(0,getMaxYOfView(alertContent,alertItemPadding),alertWidth,alertLabelHeight));
31 |
32 | alertContent.addSubview(overrideSelectLabel);
33 |
34 | var overrideSelect = createAlertSelect(instanceOverrides.valueForKey('affectedLayer').valueForKey('name'),0,NSMakeRect(0,getMaxYOfView(alertContent),alertWidth,alertSelectHeight));
35 |
36 | var overrideSelectDelegate = new MochaJSDelegate({
37 | "comboBoxSelectionDidChange:" : (function() {
38 | let currentValue = instanceOverrides[overrideSelect.indexOfSelectedItem()].valueForKey('currentValue');
39 |
40 | overrideValue.setStringValue(currentValue);
41 | })
42 | });
43 |
44 | overrideSelect.setDelegate(overrideSelectDelegate.getClassInstance());
45 |
46 | alertContent.addSubview(overrideSelect);
47 |
48 | var overrideValueLabel = createAlertLabel('New override value:',NSMakeRect(0,getMaxYOfView(alertContent,alertItemPadding),alertWidth,alertLabelHeight));
49 |
50 | alertContent.addSubview(overrideValueLabel);
51 |
52 | var overrideValue = createAlertField(instanceOverrides[0].valueForKey('currentValue'),NSMakeRect(0,getMaxYOfView(alertContent),alertWidth,alertFieldHeight));
53 |
54 | alertContent.addSubview(overrideValue);
55 |
56 | alertContent.setFrame(NSMakeRect(0,0,alertWidth,getMaxYOfView(alertContent)));
57 |
58 | alert.setAccessoryView(alertContent);
59 |
60 | var submit = alert.addButtonWithTitle('OK');
61 | var cancel = alert.addButtonWithTitle('Cancel');
62 |
63 | setKeyOrder(alert,[
64 | overrideSelect,
65 | overrideValue,
66 | submit
67 | ]);
68 |
69 | var responseCode = alert.runModal();
70 |
71 | if (responseCode == 1000) {
72 | let override = instanceOverrides[overrideSelect.indexOfSelectedItem()];
73 | let value = overrideValue.stringValue();
74 |
75 | masterInstances.forEach(instance => instance.setOverrideValue(override,value));
76 |
77 | document.sketchObject.reloadInspector();
78 |
79 | sketch.UI.message(masterInstances.length + ' symbol instances have been updated');
80 | } else return false;
81 |
82 | function getInstanceOverrides(instance) {
83 | let overrides = NSMutableArray.array();
84 |
85 | instance.sketchObject.availableOverrides().forEach(function(override){
86 | if (override.isEditable() && override.affectedLayer().class() == 'MSImmutableTextLayer') overrides.addObject(override);
87 | });
88 |
89 | return overrides.reverseObjectEnumerator().allObjects();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Customization/add-feature-link-to-manifest.js:
--------------------------------------------------------------------------------
1 | var onRun = function(context) {
2 | const sketch = require('sketch')
3 | const document = sketch.getSelectedDocument()
4 | const selections = document.selectedLayers.layers
5 |
6 | if (!selections.length) {
7 | sketch.UI.alert(`Add Feature Link to Manifest`,`Something must be selected.`)
8 |
9 | return
10 | }
11 |
12 | var fileManager = NSFileManager.defaultManager()
13 |
14 | var manifestName = 'manifest.json'
15 | var manifestPath = document.path.replace(document.sketchObject.displayName(),manifestName)
16 | manifestPath = manifestPath.replaceAll('%20',' ')
17 | var manifestData = {}
18 | var manifestExists = false
19 |
20 | if (fileManager.fileExistsAtPath(manifestPath)) {
21 | manifestData = NSString.stringWithContentsOfFile_encoding_error(manifestPath,NSUTF8StringEncoding,null)
22 | manifestData = JSON.parse(manifestData)
23 | manifestExists = true
24 | }
25 |
26 | var displayList = ['Select one…']
27 | var featureList = [
28 | {
29 | "key" : "appleAuth",
30 | "name" : "Apple Authentication"
31 | },
32 | {
33 | "key" : "backupEnabled",
34 | "name" : "Backup & Restore"
35 | },
36 | {
37 | "key" : "genius",
38 | "name" : "Genius"
39 | },
40 | {
41 | "key" : "googleAuth",
42 | "name" : "Google Authentication"
43 | },
44 | {
45 | "key" : "manageStorage",
46 | "name" : "Manage Storage"
47 | },
48 | {
49 | "key" : "photoPrint",
50 | "name" : "Photo Printing"
51 | },
52 | {
53 | "key" : "privateFolder",
54 | "name" : "Private Folder"
55 | },
56 | {
57 | "key" : "scanQRCode",
58 | "name" : "Scan QR Code (Kitamura)"
59 | },
60 | {
61 | "key" : "tagSearch",
62 | "name" : "Tag & Search"
63 | }
64 | ]
65 |
66 | featureList.forEach(f => displayList.push(f.name))
67 |
68 | if (!manifestData.featureLayers) manifestData.featureLayers = {}
69 |
70 | let addCount = 0
71 | let skipCount = 0
72 |
73 | let selectedFeature
74 | let selectionText = (selections.length == 1) ? `selection` : `${selections.length} selections`
75 |
76 | sketch.UI.getInputFromUser(
77 | `Which feature should the ${selectionText} be linked to?`,
78 | {
79 | type: sketch.UI.INPUT_TYPE.selection,
80 | possibleValues: displayList
81 | },
82 | (err,val) => {
83 | if (err || val == displayList[0]) {
84 | // most likely the user canceled the input
85 | return
86 | }
87 |
88 | if (val) {
89 | selectedFeature = featureList.find(f => f.name == val)
90 | }
91 | }
92 | )
93 |
94 | let manifestObject = manifestData.featureLayers[selectedFeature.key]
95 |
96 | if (!manifestObject) {
97 | manifestData.featureLayers[selectedFeature.key] = []
98 | manifestObject = manifestData.featureLayers[selectedFeature.key]
99 | }
100 |
101 | selections.forEach(selection => {
102 | let layerToAdd = selection.id
103 |
104 | if (selection.type == 'SymbolInstance') {
105 | let selectedOverride = selection.overrides.find(o => o.selected)
106 |
107 | if (selectedOverride) {
108 | layerToAdd = `${layerToAdd}+${selectedOverride.id}`
109 | }
110 | }
111 |
112 | if (layerToAdd && !manifestObject.includes(layerToAdd)) {
113 | manifestObject.push(layerToAdd)
114 | addCount++
115 | } else {
116 | skipCount++
117 | }
118 | })
119 |
120 | if (selectedFeature && manifestObject) {
121 | manifestData = JSON.stringify(manifestData)
122 | manifestData = NSString.stringWithFormat('%@',manifestData)
123 |
124 | if (!fileManager.fileExistsAtPath(manifestPath)) {
125 | fileManager.createDirectoryAtPath_withIntermediateDirectories_attributes_error(manifestPath.replace(manifestName,''),true,null,null)
126 | }
127 |
128 | if (addCount) manifestData.dataUsingEncoding_(NSUTF8StringEncoding).writeToFile_atomically_(manifestPath,true)
129 |
130 | let messageType = (manifestExists) ? 'updated' : 'created'
131 | let messageAdd = (addCount == 1) ? `${addCount} layer linked` : `${addCount} layers linked`
132 | let messageSkip = (skipCount) ? `, ${skipCount} layers skipped (already existed)` : ''
133 |
134 | sketch.UI.message(`Manifest ${messageType}: ${messageAdd}${messageSkip} to ${selectedFeature.name}`)
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Artboards/create-slice-around.js:
--------------------------------------------------------------------------------
1 | @import '../library.js';
2 |
3 | var document = MSDocument.currentDocument();
4 | var page = document.currentPage();
5 |
6 | var defaultSettings = {};
7 | defaultSettings.sliceType = 1;
8 | defaultSettings.sliceMargin = '100';
9 | defaultSettings.exportScales = ['.5x','1x','2x','3x'];
10 | defaultSettings.exportScale = 0;
11 | defaultSettings.exportFormats = ['JPG','PDF','PNG'];
12 | defaultSettings.exportFormat = 1;
13 |
14 | var sliceAll = function(context) {
15 | var artboards = page.artboards();
16 |
17 | createSlice(artboards,0);
18 | }
19 |
20 | var sliceSelected = function(context) {
21 | var selection = context.selection;
22 |
23 | if (!selection.length) {
24 | sketch.UI.alert('Create Slice Around Selected…','Select at least one artboard.');
25 | return false;
26 | }
27 |
28 | createSlice(selection,1);
29 | }
30 |
31 | function createSlice(scope,type) {
32 | var sliceName = (type == 0) ? 'Artboards' : 'Selections';
33 | var sliceScope = (type == 0) ? 'all' : 'selected';
34 | var sliceSettings = getSliceSettings();
35 |
36 | if (!sliceSettings) return false;
37 |
38 | var selectionSize = getSelectionSize(scope);
39 |
40 | var sliceLayer = MSSliceLayer.new();
41 | sliceLayer.setName(sliceName);
42 | sliceLayer.setBackgroundColor(MSColor.colorWithRed_green_blue_alpha(239/255,239/255,239/255,1.0));
43 | sliceLayer.frame().setX(selectionSize.minX - sliceSettings.sliceMargin);
44 | sliceLayer.frame().setY(selectionSize.minY - sliceSettings.sliceMargin);
45 | sliceLayer.frame().setWidth(selectionSize.width + (sliceSettings.sliceMargin * 2));
46 | sliceLayer.frame().setHeight(selectionSize.height + (sliceSettings.sliceMargin * 2));
47 |
48 | var sliceExport = sliceLayer.exportOptions().addExportFormat();
49 | sliceExport.setScale(defaultSettings.exportScales[sliceSettings.exportScale].slice(0,-1));
50 | sliceExport.setFileFormat(defaultSettings.exportFormats[sliceSettings.exportFormat].toLowerCase());
51 |
52 | page.addLayers([sliceLayer]);
53 |
54 | MSLayerMovement.moveToBack([sliceLayer]);
55 |
56 | sketch.UI.message('Slice created around ' + sliceScope + ' artboards');
57 | }
58 |
59 | function getSliceSettings() {
60 | var userSettings = getDocumentSettings(defaultSettings);
61 |
62 | var alert = NSAlert.alloc().init();
63 | alert.setMessageText('Create Artboard Slice…');
64 | alert.icon = getPluginAlertIcon();
65 |
66 | var alertContent = NSView.alloc().init();
67 | alertContent.setFlipped(true);
68 |
69 | var sliceMarginLabel = createAlertLabel('Slice margin:',NSMakeRect(0,0,alertWidth,alertLabelHeight));
70 | alertContent.addSubview(sliceMarginLabel);
71 |
72 | var sliceMarginValue = createAlertField(userSettings.sliceMargin,NSMakeRect(0,getMaxYOfView(alertContent),60,alertFieldHeight));
73 | alertContent.addSubview(sliceMarginValue);
74 |
75 | var exportScaleLabel = createAlertLabel('Export scale:',NSMakeRect(0,getMaxYOfView(alertContent,alertItemPadding),alertWidth,alertLabelHeight));
76 | alertContent.addSubview(exportScaleLabel);
77 |
78 | var exportScaleSelect = createAlertSelect(userSettings.exportScales,userSettings.exportScale,NSMakeRect(0,getMaxYOfView(alertContent),100,alertSelectHeight));
79 | alertContent.addSubview(exportScaleSelect);
80 |
81 | var exportFormatLabel = createAlertLabel('Export format:',NSMakeRect(0,getMaxYOfView(alertContent,alertItemPadding),alertWidth,alertLabelHeight));
82 | alertContent.addSubview(exportFormatLabel);
83 |
84 | var exportFormatSelect = createAlertSelect(userSettings.exportFormats,userSettings.exportFormat,NSMakeRect(0,getMaxYOfView(alertContent),100,alertSelectHeight));
85 | alertContent.addSubview(exportFormatSelect);
86 |
87 | alertContent.setFrame(NSMakeRect(0,0,alertWidth,getMaxYOfView(alertContent)));
88 |
89 | alert.setAccessoryView(alertContent);
90 |
91 | var submit = alert.addButtonWithTitle('OK');
92 | var cancel = alert.addButtonWithTitle('Cancel');
93 |
94 | var reset = alert.addButtonWithTitle('Defaults');
95 | reset.setAction('callAction:');
96 | reset.setCOSJSTargetFunction(function() {
97 | sliceMarginValue.setStringValue(defaultSettings.sliceMargin);
98 | exportScaleSelect.selectItemAtIndex(defaultSettings.exportScale);
99 | exportFormatSelect.selectItemAtIndex(defaultSettings.exportFormat);
100 | });
101 |
102 | setKeyOrder(alert,[
103 | sliceMarginValue,
104 | exportScaleSelect,
105 | exportFormatSelect,
106 | submit
107 | ]);
108 |
109 | var responseCode = alert.runModal();
110 |
111 | if (responseCode == 1000) {
112 | var sliceSettings = {};
113 | sliceSettings.sliceMargin = sliceMarginValue.stringValue();
114 | sliceSettings.exportScale = exportScaleSelect.indexOfSelectedItem();
115 | sliceSettings.exportFormat = exportFormatSelect.indexOfSelectedItem();
116 |
117 | setDocumentSettings(sliceSettings);
118 |
119 | return sliceSettings;
120 | } else return false;
121 | }
122 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Text Styles/convert-to-japanese-style.js:
--------------------------------------------------------------------------------
1 | const sketch = require("sketch")
2 | const document = sketch.getSelectedDocument()
3 | const selections = document.selectedLayers
4 | const sharedTextStyles = document.sharedTextStyles
5 |
6 | var onRun = function(context) {
7 | let errorCount = 0
8 | let skipCount = 0
9 | let updateCount = 0
10 |
11 | let prefixes = ["JP"]
12 | let prefix = prefixes[0] + "/"
13 |
14 | if (!selections.length) {
15 | sketch.UI.alert(`Convert to Japanese Style`,`Nothing is selected.`)
16 |
17 | return
18 | }
19 |
20 | selections.forEach(selection => {
21 | if (selection.layers) {
22 | processGroup(selection.layers)
23 | } else {
24 | processLayer(selection)
25 | }
26 | })
27 |
28 | function processGroup(layers,loop) {
29 | if (!loop) {
30 | for (var i = layers.length - 1; i >= 0; i--) {
31 | processLayer(layers[i])
32 | }
33 | } else {
34 | layers.forEach(layer => processLayer(layer))
35 | }
36 | }
37 |
38 | function processLayer(layer) {
39 | // Process symbol instances
40 | if (layer.type == "SymbolInstance") {
41 | // Get textStyle overrides for selection
42 | let textStyleOverrides = layer.overrides.filter(o => o.editable && o.property == "textStyle")
43 |
44 | // Iterate textStyle overrides
45 | textStyleOverrides.forEach(o => {
46 | // Get shared style of textStyle override
47 | let sharedStyle = sharedTextStyles.find(s => s.id === o.value)
48 |
49 | // Bail out if shared style not found
50 | if (!sharedStyle) {
51 | console.error(`Unable to find the shared style for the text style override applied to ${o.affectedLayer.name}, could be orphan or belong to a disabled library`)
52 |
53 | // Incremenet error count
54 | errorCount++
55 |
56 | return
57 | }
58 |
59 | // Get library of shared style
60 | let sharedStyleLibrary = sharedStyle.getLibrary()
61 |
62 | // Bail out if shared style library not found
63 | if (!sharedStyleLibrary) {
64 | console.error(`Unable to retrieve the library for the text style override ${sharedStyle.name} applied to ${o.affectedLayer.name}, could be a disabled library`)
65 |
66 | // Incremenet error count
67 | errorCount++
68 |
69 | return
70 | }
71 |
72 | // Get text styles from shared library
73 | let libraryTextStyles = sharedStyleLibrary.getImportableTextStyleReferencesForDocument(document)
74 |
75 | // Get shared style of JP textStyle
76 | let sharedStyleJP = libraryTextStyles.find(s => s.name === prefix + sharedStyle.name)
77 |
78 | // Bail out if JP shared style not found, or JP style already applied
79 | if (!sharedStyleJP || sharedStyle.id === sharedStyleJP.id) {
80 | // Incremenet skip count
81 | skipCount++
82 |
83 | return
84 | }
85 |
86 | // Import shared style JP textStyle
87 | let sharedStyleJPReference = sharedStyleJP.import()
88 |
89 | // Apply JP textStyle override
90 | layer.setOverrideValue(o,sharedStyleJPReference.id)
91 |
92 | // Incremenet update count
93 | updateCount++
94 | })
95 | }
96 | // Process text layers
97 | else if (layer.type == "Text") {
98 | // Get shared style of text layer
99 | let sharedStyle = layer.sharedStyle
100 |
101 | // Bail out if shared style not found
102 | if (!sharedStyle) {
103 | console.error(`Unable to find a shared style for the text layer - ${layer.name} (${layer.id})`)
104 |
105 | // Incremenet error count
106 | errorCount++
107 |
108 | return
109 | }
110 |
111 | // Get library of shared style
112 | let sharedStyleLibrary = sharedStyle.getLibrary()
113 |
114 | // Bail out if shared style library not found
115 | if (!sharedStyleLibrary) {
116 | console.error(`Unable to retrieve the library for the text style override ${sharedStyle.name} applied to ${layer.name}, could be a disabled library`)
117 |
118 | // Incremenet error count
119 | errorCount++
120 |
121 | return
122 | }
123 |
124 | // Get text styles from shared library
125 | let libraryTextStyles = sharedStyleLibrary.getImportableTextStyleReferencesForDocument(document)
126 |
127 | // Get shared style of JP textStyle
128 | let sharedStyleJP = libraryTextStyles.find(s => s.name === prefix + sharedStyle.name)
129 |
130 | // Bail out if JP shared style not found, or JP style already applied
131 | if (!sharedStyleJP || sharedStyle.id === sharedStyleJP.id) {
132 | // Incremenet skip count
133 | skipCount++
134 |
135 | return
136 | }
137 |
138 | // Import shared style JP textStyle
139 | let sharedStyleJPReference = sharedStyleJP.import()
140 |
141 | // Apply JP textStyle
142 | layer.sharedStyle = sharedStyleJPReference
143 |
144 | // Sync with JP textStyle
145 | layer.style.syncWithSharedStyle(sharedStyleJPReference)
146 |
147 | // Incremenet update count
148 | updateCount++
149 | }
150 |
151 | // If symbol instance has layers, process them too
152 | if (layer.layers) processGroup(layer.layers,1)
153 | }
154 |
155 | // Output results to user
156 | sketch.UI.message(`${updateCount} styles updated, ${skipCount} styles skipped, ${errorCount} styles had problems`)
157 | }
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/library.js:
--------------------------------------------------------------------------------
1 | @import 'delegate.js';
2 |
3 | // Shared variables
4 | var sketch = require('sketch');
5 |
6 | // Alert window variables
7 | var alertWidth = 300;
8 | var alertItemPadding = 8;
9 | var alertFieldHeight = 22;
10 | var alertLabelHeight = 20;
11 | var alertSelectHeight = 26;
12 |
13 | function createAlertField(value,frame) {
14 | var textField = NSTextField.alloc().initWithFrame(frame);
15 |
16 | textField.setStringValue(value);
17 | textField.setFont(NSFont.systemFontOfSize(13));
18 | textField.setBezeled(true);
19 |
20 | return textField;
21 | }
22 |
23 | function createAlertLabel(text,frame) {
24 | var textField = NSTextField.alloc().initWithFrame(frame);
25 |
26 | textField.setStringValue(text);
27 | textField.setFont(NSFont.systemFontOfSize(12));
28 | textField.setBezeled(false);
29 | textField.setDrawsBackground(false);
30 | textField.setEditable(false);
31 | textField.setSelectable(false);
32 |
33 | return textField;
34 | }
35 |
36 | function createAlertLabelBold(text,frame) {
37 | var textField = NSTextField.alloc().initWithFrame(frame);
38 |
39 | textField.setStringValue(text);
40 | textField.setFont(NSFont.boldSystemFontOfSize(12))
41 | textField.setBezeled(false);
42 | textField.setDrawsBackground(false);
43 | textField.setEditable(false);
44 | textField.setSelectable(false);
45 |
46 | return textField;
47 | }
48 |
49 | function createAlertPopupButton(items,select,frame) {
50 | var button = NSPopUpButton.alloc().initWithFrame(frame);
51 |
52 | button.addItemsWithTitles(items);
53 | button.selectItemAtIndex(select);
54 | //button.setFont(NSFont.systemFontOfSize(12));
55 |
56 | return button;
57 | }
58 |
59 | function createAlertRadios(options,selected,format,x,y) {
60 | var rows = options.length;
61 | var columns = 1;
62 | var buttonMatrixWidth = alertWidth;
63 | var buttonCellWidth = buttonMatrixWidth;
64 | var x = (x) ? x : 0;
65 | var y = (y) ? y : 0;
66 |
67 | if (format && format != 0) {
68 | rows = options.length / 2;
69 | columns = 2;
70 | buttonCellWidth = buttonMatrixWidth / columns;
71 | }
72 |
73 | var buttonCell = NSButtonCell.alloc().init();
74 |
75 | buttonCell.setButtonType(NSRadioButton);
76 |
77 | var buttonMatrix = NSMatrix.alloc().initWithFrame_mode_prototype_numberOfRows_numberOfColumns(
78 | NSMakeRect(x,y,buttonMatrixWidth,rows * 20),
79 | NSRadioModeMatrix,
80 | buttonCell,
81 | rows,
82 | columns
83 | );
84 |
85 | buttonMatrix.setCellSize(NSMakeSize(buttonCellWidth,20));
86 |
87 | for (i = 0; i < options.length; i++) {
88 | buttonMatrix.cells().objectAtIndex(i).setTitle(options[i]);
89 | buttonMatrix.cells().objectAtIndex(i).setTag(i);
90 | }
91 |
92 | buttonMatrix.selectCellAtRow_column(selected,0);
93 |
94 | return buttonMatrix;
95 | }
96 |
97 | function createAlertSelect(items,select,frame) {
98 | var comboBox = NSComboBox.alloc().initWithFrame(frame);
99 | var select = (select > -1) ? select : 0;
100 |
101 | comboBox.addItemsWithObjectValues(items);
102 | comboBox.selectItemAtIndex(select);
103 | comboBox.setNumberOfVisibleItems(16);
104 | comboBox.setCompletes(1);
105 |
106 | return comboBox;
107 | }
108 |
109 | function createAlertWindow(context,name,text) {
110 | var alertWindow = COSAlertWindow.new();
111 |
112 | var iconPath = context.plugin.urlForResourceNamed('icon.png').path();
113 | var icon = NSImage.alloc().initByReferencingFile(iconPath);
114 |
115 | alertWindow.setIcon(icon);
116 | alertWindow.setMessageText(name);
117 |
118 | if (text) { alertWindow.setInformativeText(text); }
119 |
120 | return alertWindow;
121 | }
122 |
123 | function getDocumentSettings(settings) {
124 | var document = sketch.getSelectedDocument();
125 | var settings = Object.assign({},settings);
126 |
127 | for (key in settings) {
128 | var value = sketch.Settings.documentSettingForKey(document,key);
129 |
130 | if (value != null) settings[key] = value;
131 | }
132 |
133 | return settings;
134 | }
135 |
136 | function getMaxYOfView(view,add) {
137 | var add = (add) ? add : 0;
138 |
139 | return CGRectGetMaxY(view.subviews().lastObject().frame()) + add;
140 | }
141 |
142 | function getPluginAlertIcon() {
143 | if (__command.pluginBundle() && __command.pluginBundle().alertIcon()) {
144 | return __command.pluginBundle().alertIcon();
145 | }
146 |
147 | return NSImage.imageNamed('plugins');
148 | }
149 |
150 | function getSelectionSize(selections) {
151 | let minX, minY, maxX, maxY
152 |
153 | minX = minY = Number.MAX_VALUE
154 | maxX = maxY = -0xFFFFFFFF
155 |
156 | selections.forEach(selection => {
157 | let rect = selection.rect()
158 |
159 | minX = Math.min(minX,CGRectGetMinX(rect))
160 | minY = Math.min(minY,CGRectGetMinY(rect))
161 | maxX = Math.max(maxX,CGRectGetMaxX(rect))
162 | maxY = Math.max(maxY,CGRectGetMaxY(rect))
163 | })
164 |
165 | return {
166 | width: maxX - minX,
167 | height: maxY - minY,
168 | minX: minX,
169 | minY: minY
170 | }
171 | }
172 |
173 | function performAction(action) {
174 | var controller = document.sketchObject.actionsController();
175 |
176 | controller.actionForID(action).doPerformAction(nil);
177 | }
178 |
179 | function setDocumentSettings(settings) {
180 | var document = sketch.getSelectedDocument();
181 |
182 | for (key in settings) {
183 | sketch.Settings.setDocumentSettingForKey(document,key,settings[key]);
184 | }
185 | }
186 |
187 | function setKeyOrder(alert,order) {
188 | for (var i = 0; i < order.length; i++) {
189 | var thisItem = order[i];
190 | var nextItem = order[i+1];
191 |
192 | if (nextItem) thisItem.setNextKeyView(nextItem);
193 | }
194 |
195 | alert.window().setInitialFirstResponder(order[0]);
196 | }
197 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/Customization/add-strings-to-manifest.js:
--------------------------------------------------------------------------------
1 | var onRun = function(context) {
2 | const sketch = require('sketch')
3 | const document = sketch.getSelectedDocument()
4 | const selections = document.selectedLayers.layers
5 |
6 | if (!selections.length) {
7 | sketch.UI.alert(`Add Translation Text to Manifest`,`Select a text layer or symbol instance with a text override.`)
8 | return
9 | }
10 |
11 | var fileManager = NSFileManager.defaultManager()
12 |
13 | var manifestName = 'manifest.json'
14 | var manifestPath = document.path.replace(escape(document.sketchObject.displayName()),manifestName)
15 | manifestPath = manifestPath.replaceAll('%20',' ')
16 | var manifestData = {}
17 | var manifestExists = false
18 |
19 | if (fileManager.fileExistsAtPath(manifestPath)) {
20 | manifestData = NSString.stringWithContentsOfFile_encoding_error(manifestPath,NSUTF8StringEncoding,null)
21 | manifestData = JSON.parse(manifestData)
22 | manifestExists = true
23 | }
24 |
25 | if (!manifestData.strings) manifestData.strings = []
26 |
27 | var addCount = 0
28 | var matchCount = 0
29 | var skipCount = 0
30 |
31 | const addString = (platform,parentArtboard,parentGroup,stringObjectID,stringExample,stringKey,stringReplace) => {
32 | let string = {
33 | platform : platform,
34 | parentArtboard : parentArtboard,
35 | parentGroup : parentGroup,
36 | stringObjectID : stringObjectID,
37 | stringExample : stringExample,
38 | stringKey : stringKey
39 | }
40 |
41 | if (stringReplace) string.stringReplace = stringReplace
42 |
43 | manifestData.strings.push(string)
44 |
45 | console.log(`${stringObjectID} (${stringExample}) added to the manifest`)
46 |
47 | addCount++
48 | }
49 |
50 | const skipString = (stringObjectID,stringExample) => {
51 | console.log(`${stringObjectID} (${stringExample}) not added to the manifest (already exists)`)
52 |
53 | skipCount++
54 | }
55 |
56 | selections.forEach(selection => {
57 | if (selection.layers) {
58 | processGroup(selection.layers)
59 | } else {
60 | processLayer(selection)
61 | }
62 | })
63 |
64 | function processGroup(layers,loop) {
65 | if (!loop) {
66 | for (var i = layers.length - 1; i >= 0; i--) {
67 | processLayer(layers[i])
68 | }
69 | } else {
70 | layers.forEach(layer => processLayer(layer))
71 | }
72 | }
73 |
74 | function processLayer(layer) {
75 | if (layer.type == 'Text' || layer.type == 'SymbolInstance' && layer.overrides.filter(o => o.property === 'stringValue' && o.editable && o.value != ' ').length) {
76 | let parentPage = layer.sketchObject.parentPage().name()
77 | let parentArtboard = `${layer.sketchObject.parentArtboard().name()} (${layer.sketchObject.parentArtboard().objectID()})`
78 | let parentGroup = (layer.sketchObject.parentGroup()) ? `${layer.sketchObject.parentGroup().name()} (${layer.sketchObject.parentGroup().objectID()})` : ''
79 |
80 | let platform = ''
81 |
82 | if (parentPage.includes('Android') || parentArtboard.includes('Android')) {
83 | platform = 'android'
84 | } else if (parentPage.includes('iOS') || parentArtboard.includes('iPad') || parentArtboard.includes('iPhone')) {
85 | platform = 'ios'
86 | }
87 |
88 | let overrides = (layer.overrides) ? layer.overrides.filter(o => o.property === 'stringValue' && o.editable && o.value != ' ') : null
89 |
90 | if (overrides) {
91 | overrides.reverse().forEach(override => {
92 | let stringObjectID = String(layer.id + '+' + override.id)
93 | let stringExample = override.value
94 | let stringKey = ''
95 | let stringReplace
96 |
97 | let stringEntry = manifestData.strings.find(s => s.stringObjectID == stringObjectID)
98 |
99 | if (!stringEntry) {
100 | if (platform != '') {
101 | let stringMatch = manifestData.strings.find(s => s.platform == platform && s.stringObjectID.includes(override.id) && s.stringExample == stringExample)
102 |
103 | if (stringMatch && stringMatch.stringKey) {
104 | stringKey = stringMatch.stringKey
105 |
106 | if (stringMatch.stringReplace) stringReplace = stringMatch.stringReplace
107 |
108 | matchCount++
109 | }
110 | }
111 |
112 | addString(platform,parentArtboard,parentGroup,stringObjectID,stringExample,stringKey,stringReplace)
113 | } else {
114 | stringEntry.platform = platform
115 | stringEntry.parentArtboard = parentArtboard
116 | stringEntry.parentGroup = parentGroup
117 | stringEntry.stringExample = stringExample
118 |
119 | skipString(stringObjectID,stringExample)
120 | }
121 | })
122 | } else {
123 | let stringObjectID = String(layer.id)
124 | let stringExample = layer.text
125 | let stringKey = ''
126 |
127 | let stringEntry = manifestData.strings.find(s => s.stringObjectID == stringObjectID)
128 |
129 | if (!stringEntry) {
130 | addString(platform,parentArtboard,parentGroup,stringObjectID,stringExample,stringKey)
131 | } else {
132 | stringEntry.platform = platform
133 | stringEntry.parentArtboard = parentArtboard
134 | stringEntry.parentGroup = parentGroup
135 | stringEntry.stringExample = stringExample
136 |
137 | skipString(stringObjectID,stringExample)
138 | }
139 | }
140 | }
141 |
142 | if (layer.layers) processGroup(layer.layers,1)
143 | }
144 |
145 | manifestData = JSON.stringify(manifestData)
146 | manifestData = NSString.stringWithFormat('%@',manifestData)
147 |
148 | if (!fileManager.fileExistsAtPath(manifestPath)) {
149 | fileManager.createDirectoryAtPath_withIntermediateDirectories_attributes_error(manifestPath.replace(manifestName,''),true,null,null)
150 | }
151 |
152 | manifestData.dataUsingEncoding_(NSUTF8StringEncoding).writeToFile_atomically_(manifestPath,true)
153 |
154 | var messageType = (manifestExists) ? 'updated' : 'created'
155 | var messageAdd = (addCount == 1) ? `${addCount} string added` : `${addCount} strings added`
156 | var messageMatch = (matchCount) ? ` (${matchCount} pre-populated)` : ''
157 | var messageSkip = (skipCount) ? `, ${skipCount} skipped (already existed)` : ''
158 |
159 | sketch.UI.message(`Manifest ${messageType}: ${messageAdd}${messageMatch}${messageSkip}`)
160 | }
161 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Resources/strings/en.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | general-button-ok
7 | OK
8 | general-button-cancel
9 | Cancel
10 | general-save-failed
11 | Unable to save settings.
12 |
13 | section-link-plugin
14 | Link Section Title
15 | section-link-problem
16 | Select one section title and one artboard to link. The section title cannot be an artboard itself.
17 | section-link-complete
18 | section title is now linked to
19 | section-unlink-plugin
20 | Unlink Section Title
21 | section-unlink-problem
22 | Select a section title to unlink.
23 | section-unlink-complete
24 | section title is no longer linked to
25 | section-unlinks-complete
26 | section titles unlinked
27 | section-titles-selected
28 | section title(s) selected
29 | section-titles-updated
30 | Section titles updated
31 | section-titles-updated-unlinked
32 | section title(s) were unlinked due to missing artboards
33 | section-settings-plugin
34 | Section Title Settings
35 | section-settings-width
36 | Width (leave blank to use existing size):
37 | section-settings-offsetX
38 | Horizontal offset:
39 | section-settings-offsetY
40 | Vertical offset:
41 |
42 | title-preclude-plugin
43 | Preclude Selected Artboards
44 | title-preclude-problem
45 | Select artboard(s) to mark as precluded from Create Artboard Titles.
46 | title-preclude-complete
47 | is now precluded from Create Artboard Titles
48 | title-precludes-complete
49 | artboards are now precluded from Create Artboard Titles
50 | title-include-plugin
51 | Include Selected Artboards
52 | title-include-problem
53 | Select artboard(s) to mark as included in Create Artboard Titles.
54 | title-include-complete
55 | is now included in Create Artboard Titles
56 | title-includes-complete
57 | artboards are now included in Create Artboard Titles
58 | title-create-plugin
59 | Create Artboard Titles
60 | title-create-problem
61 | There are no artboards on the current page, or all artboards on the page are precluded, therefore no titles to create.
62 | title-create-complete
63 | Screen titles created
64 |
65 | description-link-plugin
66 | Link Artboard Description
67 | description-link-problem
68 | Select one artboard description (a text layer with \"Wireframe/Artboard Description\" style applied) and one artboard to link.
69 | description-link-complete
70 | is now linked to
71 | description-unlink-plugin
72 | Unlink Artboard Description
73 | description-unlink-problem
74 | Select an artboard description to unlink.
75 | description-unlink-complete
76 | artboard description is no longer linked to
77 | description-unlinks-complete
78 | artboard description(s) unlinked
79 | description-selects-complete
80 | artboard description(s) selected
81 | description-set-plugin
82 | Add/Edit Artboard Description
83 | description-set-problem
84 | Select one artboard to add/edit a description.
85 | description-set-complete
86 | Artboard description added
87 | description-update-complete
88 | Artboard description updated
89 | description-updates-complete
90 | artboard description(s) updated
91 | description-updates-complete-unlinked
92 | artboard description(s) were unlinked due to missing artboards
93 | description-settings-title
94 | Artboard Description Settings
95 | description-settings-width
96 | Width (leave blank to use artboard width):
97 | description-settings-position
98 | Position:
99 | description-settings-offsetX
100 | Horizontal offset:
101 | description-settings-offsetY
102 | Vertical offset:
103 |
104 | layout-preclude-plugin
105 | Preclude Selected Artboards
106 | layout-preclude-problem
107 | Select artboard(s) to mark as precluded from Layout Artboards.
108 | layout-preclude-complete
109 | is now precluded from Layout Artboards
110 | layout-precludes-complete
111 | artboards are now precluded from Layout Artboards
112 | layout-preclude-page-complete
113 | will no longer auto-layout with Layout Artboards
114 | layout-include-plugin
115 | Include Selected Artboards
116 | layout-include-problem
117 | Select artboard(s) to mark as included in Layout Artboards.
118 | layout-include-complete
119 | is now included in Layout Artboards
120 | layout-includes-complete
121 | artboards are now included in Layout Artboards
122 | layout-include-page-complete
123 | will now auto-layout with Layout Artboards
124 | layout-artboards-plugin
125 | Layout Artboards
126 | layout-artboards-problem
127 | There are no artboards to layout.
128 | layout-artboards-complete
129 | Artboard layout complete
130 | layout-settings-title
131 | Artboard Layout Settings
132 | layout-settings-artboard-count
133 | Artboards per row:
134 | layout-settings-layout-type
135 | Layout type:
136 | layout-settings-sort-type
137 | Sort type:
138 | layout-settings-spacing-horizontal
139 | Horizontal spacing:
140 | layout-settings-spacing-vertical
141 | Vertical spacing:
142 |
143 | annotation-designate-plugin
144 | Designate Selected Annotations
145 | annotation-designate-problem
146 | Select text layer(s) to designate as annotation(s). This is required in order to link an annotation to an artboard.
147 | annotation-designate-complete
148 | is now designated as an annotation
149 | annotation-designates-complete
150 | text layers are now designated as annotations
151 | annotation-link-plugin
152 | Link Artboard Annotation
153 | annotation-link-problem-selection
154 | Select one annotation and one artboard or object to link.
155 | annotation-link-problem-textlayer
156 | Designate one of the selections as an annotation first.
157 | annotation-link-complete
158 | is now linked to
159 | annotation-unlink-complete
160 | artboard annotation is no longer linked to
161 | annotation-update-complete
162 | annotation(s) updated
163 | annotation-update-complete-unlinked
164 | annotation(s) were unlinked due to missing artboards
165 | annotation-settings
166 | Layer Annotation Settings
167 | annotation-settings-automatic
168 |
171 |
172 | wireframe-add-complete
173 | Wireframe slice created and included in Export Wireframes
174 | wireframe-include-plugin
175 | Include Selected Slice
176 | wireframe-include-problem
177 | Select one slice to include in Export Wireframes.
178 | wireframe-include-complete
179 | is now included in Export Wireframes
180 | wireframe-preclude-plugin
181 | Preclude Selected Slice
182 | wireframe-preclude-problem
183 | Select one slice to preclude from Export Wireframes.
184 | wireframe-preclude-complete
185 | is now precluded from Export Wireframes
186 | wireframe-export-plugin
187 | Export Wireframes
188 | wireframe-export-problem
189 | No wireframes were found to export.
190 | wireframe-export-complete
191 | wireframes were exported to your Downloads directory
192 | wireframe-export-intro
193 | Select which wireframes to export...
194 | wireframe-export-outro
195 | The export process may take some time, be patient.
196 |
197 |
198 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "SNCR",
3 | "description" : "A plugin of Sketch actions which are useful in the workflows of the designers at Synchronoss Technologies Inc.",
4 | "author" : "Jason Burns",
5 | "homepage" : "https://github.com/sonburn/sncr-sketch-plugin",
6 | "version" : "1.21",
7 | "identifier" : "com.sncr.sketch",
8 | "appcast" : "https://raw.githubusercontent.com/sonburn/sncr-sketch-plugin/master/appcast.xml",
9 | "icon" : "icon.png",
10 | "suppliesData": true,
11 | "commands" : [
12 | {
13 | "name" : "Link Selected Title and Artboard",
14 | "identifier" : "sections-link",
15 | "script" : "handlers.js",
16 | "handler" : "sectionsLink",
17 | "icon" : "icons/sections-link.png"
18 | },
19 | {
20 | "name" : "Unlink Selected Titles",
21 | "identifier" : "sections-unlink",
22 | "script" : "handlers.js",
23 | "handler" : "sectionsUnlink",
24 | "icon" : "icons/sections-unlink.png"
25 | },
26 | {
27 | "name" : "Update All Linked Titles on Page",
28 | "identifier" : "sections-update",
29 | "script" : "handlers.js",
30 | "handler" : "sectionsUpdate",
31 | "icon" : "icons/sections-update.png"
32 | },
33 | {
34 | "name" : "Settings…",
35 | "identifier" : "sections-settings",
36 | "script" : "handlers.js",
37 | "handler" : "sectionsSettings"
38 | },
39 | {
40 | "name" : "Create Titles for Artboards on Page",
41 | "identifier" : "titles-create",
42 | "script" : "handlers.js",
43 | "handler" : "titlesCreate",
44 | "icon" : "icons/titles-create.png"
45 | },
46 | {
47 | "name" : "Include Selected Artboards",
48 | "identifier" : "titles-include",
49 | "script" : "handlers.js",
50 | "handler" : "titlesInclude",
51 | "icon" : "icons/titles-include.png"
52 | },
53 | {
54 | "name" : "Preclude Selected Artboards",
55 | "identifier" : "titles-preclude",
56 | "script" : "handlers.js",
57 | "handler" : "titlesPreclude",
58 | "icon" : "icons/titles-preclude.png"
59 | },
60 | {
61 | "name" : "Settings…",
62 | "identifier" : "titles-settings",
63 | "script" : "handlers.js",
64 | "handler" : "titlesSettings"
65 | },
66 | {
67 | "name" : "Add/Edit Description on Selected Artboard",
68 | "identifier" : "descriptions-set",
69 | "script" : "handlers.js",
70 | "handler" : "descriptionsSet",
71 | "icon" : "icons/descriptions-set.png"
72 | },
73 | {
74 | "name" : "Link Selected Description and Artboard",
75 | "identifier" : "descriptions-link",
76 | "script" : "handlers.js",
77 | "handler" : "descriptionsLink",
78 | "icon" : "icons/descriptions-link.png"
79 | },
80 | {
81 | "name" : "Unlink Selected Descriptions",
82 | "identifier" : "descriptions-unlink",
83 | "script" : "handlers.js",
84 | "handler" : "descriptionsUnlink",
85 | "icon" : "icons/descriptions-unlink.png"
86 | },
87 | {
88 | "name" : "Update All Linked Descriptions on Page",
89 | "identifier" : "descriptions-update",
90 | "script" : "handlers.js",
91 | "handler" : "descriptionsUpdate",
92 | "icon" : "icons/descriptions-update.png"
93 | },
94 | {
95 | "name" : "Settings…",
96 | "identifier" : "descriptions-settings",
97 | "script" : "handlers.js",
98 | "handler" : "descriptionsSettings"
99 | },
100 | {
101 | "name" : "Layout Artboards on Page",
102 | "identifier" : "layout-update",
103 | "script" : "handlers.js",
104 | "handlers" : {
105 | "actions" : {
106 | "run" : "layoutUpdate",
107 | "LayersMoved.finish" : "layoutUpdate",
108 | "ResizeArtboardToFit.finish" : "layoutUpdate"
109 | }
110 | },
111 | "icon" : "icons/layout-update.png"
112 | },
113 | {
114 | "name" : "Include Selected Artboards",
115 | "identifier" : "layout-includeSelected",
116 | "script" : "handlers.js",
117 | "handler" : "layoutIncludeSelected",
118 | "icon" : "icons/layout-include.png"
119 | },
120 | {
121 | "name" : "Preclude Selected Artboards",
122 | "identifier" : "layout-precludeSelected",
123 | "script" : "handlers.js",
124 | "handler" : "layoutPrecludeSelected",
125 | "icon" : "icons/layout-preclude.png"
126 | },
127 | {
128 | "name" : "Enable Automatic Layout for Page",
129 | "identifier" : "layout-includePage",
130 | "script" : "handlers.js",
131 | "handler" : "layoutIncludePage",
132 | "icon" : "icons/layout-include-page.png"
133 | },
134 | {
135 | "name" : "Disable Automatic Layout for Page",
136 | "identifier" : "layout-precludePage",
137 | "script" : "handlers.js",
138 | "handler" : "layoutPrecludePage",
139 | "icon" : "icons/layout-preclude-page.png"
140 | },
141 | {
142 | "name" : "Settings for Page…",
143 | "identifier" : "layout-settings",
144 | "script" : "handlers.js",
145 | "handler" : "layoutSettings"
146 | },
147 | {
148 | "name" : "Create Annotations for Selections",
149 | "identifier" : "annotations-create",
150 | "script" : "handlers.js",
151 | "handlers" : {
152 | "actions" : {
153 | "run" : "annotationsCreate",
154 | "AddFlow.finish" : "annotationsCreate"
155 | }
156 | },
157 | "icon" : "icons/annotations-create.png"
158 | },
159 | {
160 | "name" : "Designate Selected Layers as Annotations",
161 | "identifier" : "annotations-designate",
162 | "script" : "handlers.js",
163 | "handler" : "annotationsDesignate",
164 | "icon" : "icons/annotations-designate.png"
165 | },
166 | {
167 | "name" : "Link Selected Annotation and Object/Artboard",
168 | "identifier" : "annotations-link",
169 | "script" : "handlers.js",
170 | "handler" : "annotationsLink",
171 | "icon" : "icons/annotations-link.png"
172 | },
173 | {
174 | "name" : "Force Selected Annotations Position to Right",
175 | "identifier" : "annotations-position",
176 | "script" : "handlers.js",
177 | "handler" : "annotationsPosition"
178 | },
179 | {
180 | "name" : "Update All Linked Annotations on Page",
181 | "identifier" : "annotations-update",
182 | "script" : "handlers.js",
183 | "handler" : "annotationsUpdate",
184 | "icon" : "icons/annotations-update.png"
185 | },
186 | {
187 | "name" : "Settings…",
188 | "identifier" : "annotations-settings",
189 | "script" : "handlers.js",
190 | "handler" : "annotationsSettings"
191 | },
192 | {
193 | "name" : "Add/Edit Condition for Selected Layer",
194 | "identifier" : "conditions-set",
195 | "script" : "handlers.js",
196 | "handler" : "conditionsSet"
197 | },
198 | {
199 | "name" : "Add Wireframe Slice",
200 | "identifier" : "wireframes-add",
201 | "script" : "handlers.js",
202 | "handler" : "wireframesAdd",
203 | "icon" : "icons/wireframes-add.png"
204 | },
205 | {
206 | "name" : "Include Selected Slice",
207 | "identifier" : "wireframes-include",
208 | "script" : "handlers.js",
209 | "handler" : "wireframesInclude",
210 | "icon" : "icons/wireframes-include.png"
211 | },
212 | {
213 | "name" : "Preclude Selected Slice",
214 | "identifier" : "wireframes-preclude",
215 | "script" : "handlers.js",
216 | "handler" : "wireframesPreclude",
217 | "icon" : "icons/wireframes-preclude.png"
218 | },
219 | {
220 | "name" : "Export Wireframes…",
221 | "identifier" : "wireframes-export",
222 | "script" : "handlers.js",
223 | "handler" : "wireframesExport",
224 | "icon" : "icons/wireframes-export.png"
225 | },
226 | {
227 | "name" : "Create Artboard Instance of Artboard Symbol",
228 | "identifier" : "artboards-create-artboard-instance-of-artboard-symbol",
229 | "script" : "Artboards/create-artboard-instance-of-artboard-symbol.js"
230 | },
231 | {
232 | "name" : "Create Slice Around All…",
233 | "identifier" : "artboards-create-slice-around-all",
234 | "script" : "Artboards/create-slice-around.js",
235 | "handler" : "sliceAll"
236 | },
237 | {
238 | "name" : "Create Slice Around Selected…",
239 | "identifier" : "artboards-create-slice-around-selected",
240 | "script" : "Artboards/create-slice-around.js",
241 | "handler" : "sliceSelected"
242 | },
243 | {
244 | "name" : "Increment Version Numbers for Selected…",
245 | "identifier" : "artboards-increment-version-numbers-for-selected",
246 | "script" : "Artboards/increment-version-numbers.js"
247 | },
248 | {
249 | "name" : "Increment Version Numbers for Artboards on Page…",
250 | "identifier" : "artboards-increment-version-numbers-on-page",
251 | "script" : "Artboards/increment-version-numbers.js"
252 | },
253 | {
254 | "name" : "Insert Mask in Selected Artboards",
255 | "identifier" : "artboards-insert-mask-in-selected",
256 | "script" : "Artboards/insert-mask-in.js"
257 | },
258 | {
259 | "name" : "Update Export Settings for All on Page",
260 | "identifier" : "artboards-update-export-settings-for-all-on-page",
261 | "script" : "Artboards/update-export-settings-for-all-on-page.js"
262 | },
263 | {
264 | "name" : "Get Current ID",
265 | "identifier" : "document-get-current-id",
266 | "script" : "Document/get-current-id.js"
267 | },
268 | {
269 | "name" : "Set Custom ID",
270 | "identifier" : "document-set-custom-id",
271 | "script" : "Document/set-custom-id.js"
272 | },
273 | {
274 | "name" : "Set Random ID",
275 | "identifier" : "document-set-random-id",
276 | "script" : "Document/set-random-id.js"
277 | },
278 | {
279 | "name" : "Export Page to Zeplin",
280 | "identifier" : "export-page-to-zeplin",
281 | "script" : "Export/page-to-zeplin.js"
282 | },
283 | {
284 | "name" : "Update Content Bounds",
285 | "identifier" : "group-update-content-bounds",
286 | "script" : "Groups/update-content-bounds.js"
287 | },
288 | {
289 | "name" : "Create Hotspot Over Selected Overrides",
290 | "identifier" : "hotspots-create-over-selected-overrides",
291 | "script" : "Hotspots/create-over-selected-overrides.js",
292 | "shortcut" : "cmd option shift h",
293 | },
294 | {
295 | "name" : "Toggle All Hotspots in Document",
296 | "identifier" : "hotspots-toggle-all-in-document",
297 | "script" : "Hotspots/toggle-all-in-document.js",
298 | "icon" : "icons/other-hotspots.png"
299 | },
300 | {
301 | "name" : "Disable Style Overrides for All Symbols on Page",
302 | "identifier" : "symbols-disable-style-overrides",
303 | "script" : "Symbols/disable-style-overrides.js"
304 | },
305 | {
306 | "name" : "Replace Text Override for Instances of Selected",
307 | "identifier" : "symbols-replace-override-for-instances-of-selection",
308 | "script" : "Symbols/replace-override-for-instances-of-selection.js"
309 | },
310 | {
311 | "name" : "Replace Override Fills with Swatch",
312 | "identifier" : "symbols-replace-override-fills-with-swatch",
313 | "script" : "Symbols/replace-override-fills-with-swatch.js"
314 | },
315 | {
316 | "name" : "Convert to Japanese Style",
317 | "identifier" : "textstyles-convert-to-japanese-style",
318 | "script" : "Text Styles/convert-to-japanese-style.js"
319 | },
320 | {
321 | "name" : "Recreate and Remove Original",
322 | "identifier" : "textlayers-recreate-and-remove-original",
323 | "script" : "Text Layers/recreate-and-remove-original.js"
324 | },
325 | {
326 | "name" : "Update Text to Match Style Name",
327 | "identifier" : "textlayers-update-text-to-match-style-name",
328 | "script" : "Text Layers/update-text-to-match-style-name.js"
329 | },
330 | {
331 | "name" : "Add Feature Link to Manifest",
332 | "identifier" : "customization-add-feature-link-to-manifest",
333 | "script" : "Customization/add-feature-link-to-manifest.js"
334 | },
335 | {
336 | "name" : "Add Strings to Manifest",
337 | "identifier" : "customization-add-strings-to-manifest",
338 | "script" : "Customization/add-strings-to-manifest.js"
339 | },
340 | {
341 | "name" : "Run Manifest Audit",
342 | "identifier" : "customization-run-manifest-audit",
343 | "script" : "Customization/run-manifest-audit.js"
344 | },
345 | {
346 | "script": "Data/script.js",
347 | "handlers": {
348 | "actions": {
349 | "Startup" : "onStartup",
350 | "Shutdown" : "onShutdown",
351 | "SupplyTextFromFile" : "onSupplyTextFromFile",
352 | "SupplyImageFromFolder" : "onSupplyImageFromFolder",
353 | "SupplyRandomTextFromFile" : "onSupplyRandomTextFromFile",
354 | "SupplyRandomImageFromFolder" : "onSupplyRandomImageFromFolder"
355 | }
356 | }
357 | }
358 | ],
359 | "menu" : {
360 | "title" : "SNCR",
361 | "items" : [
362 | {
363 | "title" : "Section Titles",
364 | "items" : [
365 | "sections-link",
366 | "sections-unlink",
367 | "sections-update",
368 | "-",
369 | "sections-settings"
370 | ]
371 | },
372 | "-",
373 | {
374 | "title" : "Artboard Titles",
375 | "items" : [
376 | "titles-create",
377 | "titles-include",
378 | "titles-preclude",
379 | "-",
380 | "titles-settings"
381 | ]
382 | },
383 | {
384 | "title" : "Artboard Descriptions",
385 | "items" : [
386 | "descriptions-set",
387 | "descriptions-link",
388 | "descriptions-unlink",
389 | "descriptions-update",
390 | "-",
391 | "descriptions-settings"
392 | ]
393 | },
394 | "-",
395 | {
396 | "title" : "Layer Annotations",
397 | "items" : [
398 | "annotations-create",
399 | "annotations-designate",
400 | "annotations-link",
401 | "annotations-position",
402 | "annotations-update",
403 | "-",
404 | "annotations-settings"
405 | ]
406 | },
407 | "-",
408 | {
409 | "title" : "Page Layout",
410 | "items" : [
411 | "layout-update",
412 | "layout-includeSelected",
413 | "layout-precludeSelected",
414 | "-",
415 | "layout-includePage",
416 | "layout-precludePage",
417 | "-",
418 | "layout-settings"
419 | ]
420 | },
421 | "-",
422 | {
423 | "title" : "Wireframes",
424 | "items" : [
425 | "wireframes-add",
426 | "wireframes-include",
427 | "wireframes-preclude",
428 | "wireframes-export"
429 | ]
430 | },
431 | "-",
432 | {
433 | "title" : "Customization Portal",
434 | "items" : [
435 | "customization-add-feature-link-to-manifest",
436 | "customization-add-strings-to-manifest",
437 | "-",
438 | "customization-run-manifest-audit"
439 | ]
440 | },
441 | "-",
442 | {
443 | "title" : "Other",
444 | "items" : [
445 | {
446 | "title" : "Artboards",
447 | "items" : [
448 | "artboards-create-artboard-instance-of-artboard-symbol",
449 | "-",
450 | "artboards-create-slice-around-all",
451 | "artboards-create-slice-around-selected",
452 | "-",
453 | "artboards-increment-version-numbers-on-page",
454 | "-",
455 | "artboards-insert-mask-in-selected",
456 | "-",
457 | "artboards-update-export-settings-for-all-on-page"
458 | ]
459 | },
460 | {
461 | "title" : "Document",
462 | "items" : [
463 | "document-get-current-id",
464 | "document-set-custom-id",
465 | "document-set-random-id"
466 | ]
467 | },
468 | {
469 | "title" : "Export",
470 | "items" : [
471 | "export-page-to-zeplin"
472 | ]
473 | },
474 | {
475 | "title" : "Groups",
476 | "items" : [
477 | "group-update-content-bounds"
478 | ]
479 | },
480 | {
481 | "title" : "Hotspots",
482 | "items" : [
483 | "hotspots-create-over-selected-overrides",
484 | "hotspots-toggle-all-in-document"
485 | ]
486 | },
487 | {
488 | "title" : "Symbols",
489 | "items" : [
490 | "symbols-disable-style-overrides",
491 | "symbols-replace-override-for-instances-of-selection",
492 | "symbols-replace-override-fills-with-swatch"
493 | ]
494 | },
495 | {
496 | "title" : "Text Styles",
497 | "items" : [
498 | "textstyles-convert-to-japanese-style"
499 | ]
500 | },
501 | {
502 | "title" : "Text Layers",
503 | "items" : [
504 | "textlayers-recreate-and-remove-original",
505 | "textlayers-update-text-to-match-style-name"
506 | ]
507 | }
508 | ]
509 | }
510 | ]
511 | }
512 | }
513 |
--------------------------------------------------------------------------------
/SNCR.sketchplugin/Contents/Sketch/functions.js:
--------------------------------------------------------------------------------
1 | @import 'delegate.js';
2 |
3 | var sketch = require("sketch");
4 | var debug = false;
5 |
6 | var sncr = {
7 | init: function(context,command) {
8 | this.pluginDomain = "com.sncr.sketch";
9 |
10 | this.document = context.document || context.actionContext.document;
11 | this.selection = context.selection;
12 | this.command = context.command;
13 | this.pages = this.document.pages();
14 | this.page = this.document.currentPage();
15 | this.data = this.document.documentData();
16 | this.symbols = this.data.allSymbols();
17 | this.symbolsPage = this.data.symbolsPage();
18 |
19 | this.localeID = "en";
20 | this.stringsPath = context.plugin.urlForResourceNamed("strings/" + this.localeID + ".plist").path();
21 | this.strings = NSDictionary.dictionaryWithContentsOfFile(this.stringsPath);
22 |
23 | this.parentGroupName = "SNCR";
24 | this.artboardNoteGroupName = "Annotations";
25 | this.descriptionsGroupName = "Descriptions";
26 | this.titlesGroupName = "Titles";
27 |
28 | this.annotations.config = {
29 | connectionsGroupKey : "connectionsGroup",
30 | connectionsGroupName : "Connections",
31 | annotationXOffset : 48,
32 | annotationYOffset : 0,
33 | annotationWidth : 240,
34 | annotationSpacing : 8,
35 | annotationLinkKey : "linkedToObject",
36 | annotationLinkTypeKey : "linkType",
37 | annotationLinkTypeValue : "annotation",
38 | annotationParentKey : "linkedParentArtboard",
39 | annotationLinkPrefix : "🔗 ",
40 | annotationStyleData : {
41 | fontFace : "Helvetica Neue",
42 | fontSize : 14,
43 | fontColor : "#000000",
44 | lineHeight : 18,
45 | textAlignment : 0
46 | },
47 | annotationArrowName : "Arrow",
48 | annotationArrowStrokeWidth : 1,
49 | annotationArrowStrokeColor : "#00AEEF",
50 | annotationArrowStartType : 4, // 0 = None, 1 = Angled Arrow, 2 = Solid Arrow, 3 = Line, 4 = Empty Circle, 5 = Solid Circle, 6 = Empty Square, 7 = Solid Square
51 | annotationArrowStartXOffset : 0,
52 | annotationArrowStartYOffset : 0,
53 | annotationArrowEndType : 2, // 0 = None, 1 = Angled Arrow, 2 = Solid Arrow, 3 = Line, 4 = Empty Circle, 5 = Solid Circle, 6 = Empty Square, 7 = Solid Square
54 | annotationArrowEndXOffset : -12,
55 | annotationArrowEndYOffset : 9 // Half annotationStyleData lineHeight
56 | }
57 |
58 | this.descriptions.config = {
59 | descriptionStyleName : "Wireframe/Artboard Description",
60 | descriptionStyleData : {
61 | fontFace : "Neue Haas Grotesk Text Std 55 Roman",
62 | fontSize : 14,
63 | lineHeight : 18,
64 | textAlignment : 0
65 | },
66 | descriptionXOffset : 0,
67 | descriptionYOffset : 24,
68 | descriptionLinkKey : "linkedToArtboard",
69 | descriptionLinkTypeKey : "linkType",
70 | descriptionLinkTypeValue : "description",
71 | descriptionLinkPrefix : "🔗 "
72 | }
73 |
74 | this.layout.config = {
75 | featureKey : "layoutArtboards",
76 | pageNamePrefix : "🔹 "
77 | }
78 |
79 | this.sections.config = {
80 | symbolMasterKey : "sectionTitleSymbol",
81 | titleLinkKey : "sncrScreenTitleLinkedTo",
82 | titleLinkPrefix : "🔗 "
83 | }
84 |
85 | this.titles.config = {
86 | featureKey : "createArtboardTitles"
87 | }
88 |
89 | this.wireframes.config = {
90 | featureKey : "wireframeExport"
91 | }
92 |
93 | if (command) {
94 | switch (command) {
95 | case "sections-link" :
96 | this.sections.linkSelected(context);
97 | break;
98 | case "sections-unlink" :
99 | this.sections.unlinkSelected(context);
100 | break;
101 | case "sections-update" :
102 | this.sections.updateAllOnPage(context);
103 | break;
104 | case "sections-settings" :
105 | this.sections.settings(context);
106 | break;
107 | case "titles-create" :
108 | this.titles.create(context,"create");
109 | break;
110 | case "titles-include" :
111 | this.titles.include(context);
112 | break;
113 | case "titles-preclude" :
114 | this.titles.preclude(context);
115 | break;
116 | case "titles-settings" :
117 | this.titles.settings(context);
118 | break;
119 | case "descriptions-set" :
120 | this.descriptions.addEdit(context);
121 | break;
122 | case "descriptions-link" :
123 | this.descriptions.linkSelected(context);
124 | break;
125 | case "descriptions-unlink" :
126 | this.descriptions.unlinkSelected(context);
127 | break;
128 | case "descriptions-update" :
129 | this.descriptions.updateAllOnPage(context);
130 | break;
131 | case "descriptions-settings" :
132 | this.descriptions.settings(context);
133 | break;
134 | case "layout-update" :
135 | this.layout.update(context);
136 | break;
137 | case "layout-include-selected" :
138 | this.layout.includeSelected(context);
139 | break;
140 | case "layout-preclude-selected" :
141 | this.layout.precludeSelected(context);
142 | break;
143 | case "layout-include-page" :
144 | this.layout.includePage(context);
145 | break;
146 | case "layout-preclude-page" :
147 | this.layout.precludePage(context);
148 | break;
149 | case "layout-settings" :
150 | this.layout.settings(context);
151 | break;
152 | case "annotations-create" :
153 | this.annotations.annotateSelected(context);
154 | break;
155 | case "annotations-designate" :
156 | this.annotations.designateSelected();
157 | break;
158 | case "annotations-link" :
159 | this.annotations.linkSelected(context);
160 | break;
161 | case "annotations-position" :
162 | this.annotations.updatePosition(context);
163 | break;
164 | case "annotations-update" :
165 | this.annotations.updateAnnotations(context);
166 | break;
167 | case "annotations-settings" :
168 | this.annotations.settings(context);
169 | break;
170 | case "wireframes-add" :
171 | this.wireframes.addNew(context);
172 | break;
173 | case "wireframes-include" :
174 | this.wireframes.include(context);
175 | break;
176 | case "wireframes-preclude" :
177 | this.wireframes.preclude(context);
178 | break;
179 | case "wireframes-export" :
180 | this.wireframes.export(context);
181 | break;
182 | }
183 | }
184 | }
185 | }
186 |
187 | sncr.annotations = {
188 | annotateSelected: function(context) {
189 | // This function can be called by AddFlow.finish, which is only triggered if user applies a flow directly to an
190 | // object (not by updating the flow of a nested hotspot). In this case, context.actionContext will be present.
191 | if (context.actionContext) {
192 | if (sncr.annotations.settings(context,"create").autoAnnotate == 1) {
193 | // Wait a couple seconds for processing to complete
194 | COScript.currentCOScript().scheduleWithInterval_jsFunction(2, function() {
195 | annotateSelections(context.actionContext.document.selectedLayers().layers());
196 | });
197 | } else return;
198 | } else {
199 | var selections = sncr.selection;
200 |
201 | if (!selections.length) {
202 | sketch.UI.message("Nothing is selected");
203 |
204 | return;
205 | }
206 |
207 | annotateSelections(selections);
208 | }
209 |
210 | function annotateSelections(layers) {
211 | var updatedArtboards = NSMutableArray.array();
212 |
213 | layers.forEach(layer => {
214 | var artboardID = layer.parentArtboard().objectID();
215 |
216 | if (layer.flow() && layer.flow().destinationArtboardID()) {
217 | sncr.annotations.createAnnotation(layer,layer.flow().destinationArtboardID());
218 |
219 | if (!updatedArtboards.containsObject(artboardID)) {
220 | updatedArtboards.addObject(artboardID);
221 | }
222 | }
223 |
224 | // If context.actionContext present (called by AddFlow.finish), don't bother drilling deeper as the
225 | // action is not triggered by updating the flow of a nested hotspot.
226 | if (!context.actionContext) {
227 | if (layer instanceof MSSymbolInstance && layer.overrides()) {
228 | var overrides = sketch.fromNative(layer).overrides.filter(o => o.editable && o.property === 'flowDestination' && !['','null'].includes(o.value));
229 |
230 | overrides.forEach(o => {
231 | let value = o.value;
232 | let path = (o.path.includes('/')) ? o.path.substr(0,o.path.indexOf('/')) : o.path;
233 |
234 | sncr.annotations.createAnnotation(layer,value,path);
235 |
236 | if (!updatedArtboards.containsObject(artboardID)) {
237 | updatedArtboards.addObject(artboardID);
238 | }
239 | });
240 | }
241 | }
242 | });
243 |
244 | updatedArtboards.forEach(artboardID => {
245 | sncr.annotations.updateAnnotations(context,artboardID);
246 | });
247 |
248 | if (!updatedArtboards.length) sketch.UI.message('The selected layer(s) have nothing to annotate');
249 | }
250 | },
251 | designateSelected: function(annotation) {
252 | if (annotation) {
253 | sncr.command.setValue_forKey_onLayer(sncr.annotations.config.annotationLinkTypeValue,sncr.annotations.config.annotationLinkTypeKey,annotation);
254 |
255 | var annotationName = annotation.name().split(/\r\n|\r|\n/g)[0];
256 |
257 | log(annotationName + sncr.strings['annotation-designate-complete']);
258 |
259 | sketch.UI.message(annotationName + sncr.strings['annotation-designate-complete']);
260 | } else {
261 | if (!sncr.selection) {
262 | sketch.UI.alert(sncr.strings['annotation-designate-plugin'],sncr.strings['annotation-designate-problem']);
263 |
264 | return;
265 | }
266 |
267 | var selectionLoop = sncr.selection.objectEnumerator(),
268 | selection,
269 | annotationName,
270 | count = 0;
271 |
272 | while (selection = selectionLoop.nextObject()) {
273 | sncr.command.setValue_forKey_onLayer(sncr.annotations.config.annotationLinkTypeValue,sncr.annotations.config.annotationLinkTypeKey,selection);
274 |
275 | annotationName = selection.name().split(/\r\n|\r|\n/g)[0];
276 |
277 | log(annotationName + sncr.strings['annotation-designate-complete']);
278 |
279 | count++;
280 | }
281 |
282 | sketch.UI.message(((count == 1) ? annotationName : count) + sncr.strings['annotation-designate-complete']);
283 | }
284 | },
285 | createAnnotation: function(linkedObject,destinationArtboardID,flowObjectID) {
286 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName);
287 | var noteGroup = getChildGroup(parentGroup,sncr.artboardNoteGroupName);
288 |
289 | var destinationArtboardName;
290 |
291 | if (destinationArtboardID == "back") {
292 | destinationArtboardName = "Back to originating screen";
293 | } else if (sncr.data.layerWithID(destinationArtboardID)) {
294 | destinationArtboardName = sncr.data.layerWithID(destinationArtboardID).name();
295 | } else {
296 | destinationArtboardName = "Unknown";
297 | }
298 |
299 | var artboardAnnotations = sncr.annotations.getAnnotations(linkedObject.parentArtboard().objectID());
300 |
301 | var predicate;
302 |
303 | if (flowObjectID) {
304 | predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.annotationLinkKey + " == '" + linkedObject.objectID() + "' && function(userInfo,'valueForKeyPath:',%@).flowObjectID == '" + flowObjectID + "'",sncr.pluginDomain,sncr.pluginDomain);
305 | } else {
306 | predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.annotationLinkKey + " == '" + linkedObject.objectID() + "' && function(userInfo,'valueForKeyPath:',%@).flowObjectID == nil",sncr.pluginDomain,sncr.pluginDomain);
307 | }
308 |
309 | var existingNote = artboardAnnotations.filteredArrayUsingPredicate(predicate).firstObject();
310 |
311 | if (!existingNote) {
312 | var annotation = MSTextLayer.new();
313 | annotation.setStringValue(destinationArtboardName);
314 | annotation.setName(destinationArtboardName);
315 | annotation.setFont(NSFont.fontWithName_size(sncr.annotations.config.annotationStyleData.fontFace + " Bold",sncr.annotations.config.annotationStyleData.fontSize));
316 | annotation.setLineHeight(sncr.annotations.config.annotationStyleData.lineHeight);
317 | annotation.setTextColor(MSImmutableColor.colorWithSVGString(sncr.annotations.config.annotationStyleData.fontColor));
318 | annotation.setTextBehaviour(1);
319 | annotation.frame().setWidth(sncr.annotations.config.annotationWidth);
320 |
321 | noteGroup.addLayers([annotation]);
322 |
323 | sncr.command.setValue_forKey_onLayer(linkedObject.objectID(),sncr.annotations.config.annotationLinkKey,annotation);
324 | sncr.command.setValue_forKey_onLayer(sncr.annotations.config.annotationLinkTypeValue,sncr.annotations.config.annotationLinkTypeKey,annotation);
325 | sncr.command.setValue_forKey_onLayer(linkedObject.parentArtboard().objectID(),sncr.annotations.config.annotationParentKey,annotation);
326 |
327 | if (flowObjectID) {
328 | sncr.command.setValue_forKey_onLayer(flowObjectID,'flowObjectID',annotation);
329 | }
330 | } else {
331 | var existingString = existingNote.stringValue();
332 |
333 | var noteTitleFont = NSFont.fontWithName_size(sncr.annotations.config.annotationStyleData.fontFace + " Bold",sncr.annotations.config.annotationStyleData.fontSize);
334 | var noteDescFont = NSFont.fontWithName_size(sncr.annotations.config.annotationStyleData.fontFace,sncr.annotations.config.annotationStyleData.fontSize);
335 |
336 | if (existingString.indexOf("\n") != -1 || existingString.indexOf("\r") != -1) {
337 | var n = existingString.indexOf("\n");
338 | var r = existingString.indexOf("\r");
339 | var returnIndex;
340 | var returnType;
341 |
342 | if (n != -1 && r != -1) {
343 | if (n < r) {
344 | returnIndex = n;
345 | returnType = "\n";
346 | } else {
347 | returnIndex = r;
348 | returnType = "\r";
349 | }
350 | } else if (n != -1) {
351 | returnIndex = n;
352 | returnType = "\n";
353 | } else {
354 | returnIndex = r;
355 | returnType = "\r";
356 | }
357 |
358 | var rangeBegin = returnIndex + 1;
359 | var rangeEnd = existingString.length() - rangeBegin;
360 | var newString = destinationArtboardName + "\n" + existingString.substr(rangeBegin,rangeEnd);
361 |
362 | existingNote.setStringValue(newString);
363 | existingNote.setFont(noteTitleFont);
364 |
365 | var rangeBegin = newString.indexOf("\n") + 1;
366 | var rangeEnd = newString.length - rangeBegin;
367 | var range = NSMakeRange(rangeBegin,rangeEnd);
368 |
369 | existingNote.addAttribute_value_forRange(NSFontAttributeName,noteDescFont,range);
370 | } else {
371 | existingNote.setStringValue(destinationArtboardName);
372 | existingNote.setFont(noteTitleFont);
373 | }
374 |
375 | existingNote.setName(destinationArtboardName);
376 |
377 | existingNote.setLineHeight(sncr.annotations.config.annotationStyleData.lineHeight);
378 | existingNote.setTextColor(MSImmutableColor.colorWithSVGString(sncr.annotations.config.annotationStyleData.fontColor));
379 | existingNote.setTextBehaviour(1);
380 | existingNote.frame().setWidth(sncr.annotations.config.annotationWidth);
381 | }
382 | },
383 | getAnnotations: function(artboardID) {
384 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName);
385 | var noteGroup = getChildGroup(parentGroup,sncr.artboardNoteGroupName);
386 |
387 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.annotationLinkKey + " != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.annotationLinkTypeKey + " == " + sncr.annotations.config.annotationLinkTypeValue,sncr.pluginDomain);
388 | var annotations = noteGroup.children().filteredArrayUsingPredicate(predicate);
389 |
390 | var annotationArray = NSMutableArray.array();
391 |
392 | annotations.forEach(annotation => {
393 | var linkedObjectID = sncr.command.valueForKey_onLayer(sncr.annotations.config.annotationLinkKey,annotation);
394 | var linkedObject = sncr.data.layerWithID(linkedObjectID);
395 |
396 | if (linkedObject && linkedObject.parentArtboard().objectID() == artboardID) {
397 | annotationArray.addObject(annotation);
398 | }
399 | });
400 |
401 | return annotationArray;
402 | },
403 | linkSelected: function(context) {
404 | // If nothing is selected, or two objects are not selected...
405 | if (!sncr.selection || sncr.selection.count() != 2) {
406 | // Display feedback
407 | sketch.UI.alert(sncr.strings["annotation-link-plugin"],sncr.strings["annotation-link-problem-selection"]);
408 |
409 | return;
410 | }
411 |
412 | // Selection variables
413 | var firstObject = sncr.selection.firstObject();
414 | var firstObjectClass = firstObject.class();
415 | var firstObjectAnnotation = sncr.command.valueForKey_onLayer(sncr.annotations.config.annotationLinkTypeKey,firstObject);
416 | var lastObject = sncr.selection.lastObject();
417 | var lastObjectClass = lastObject.class();
418 | var lastObjectAnnotation = sncr.command.valueForKey_onLayer(sncr.annotations.config.annotationLinkTypeKey,lastObject);
419 | var annotation;
420 | var source;
421 |
422 | // If only the first object is a text layer...
423 | if (firstObjectClass == "MSTextLayer" && lastObjectClass != "MSTextLayer") {
424 | annotation = firstObject;
425 | source = lastObject;
426 | }
427 | // If only the second object is a text layer...
428 | else if (firstObjectClass != "MSTextLayer" && lastObjectClass == "MSTextLayer") {
429 | annotation = lastObject;
430 | source = firstObject;
431 | }
432 | // If the first object is the annotation...
433 | else if (firstObjectAnnotation == 'annotation' && !lastObjectAnnotation) {
434 | annotation = firstObject;
435 | source = lastObject;
436 | }
437 | // If the second object is the annotation...
438 | else if (!firstObjectAnnotation && lastObjectAnnotation == 'annotation') {
439 | annotation = lastObject;
440 | source = firstObject;
441 | }
442 | // If neither object is a text layer, or both are...
443 | else {
444 | // Display feedback
445 | sketch.UI.alert(sncr.strings["annotation-link-plugin"],sncr.strings["annotation-link-problem-textlayer"]);
446 |
447 | return;
448 | }
449 |
450 | // Set values on annotation
451 | sncr.command.setValue_forKey_onLayer(source.objectID(),sncr.annotations.config.annotationLinkKey,annotation);
452 | sncr.command.setValue_forKey_onLayer(sncr.annotations.config.annotationLinkTypeValue,sncr.annotations.config.annotationLinkTypeKey,annotation);
453 | sncr.command.setValue_forKey_onLayer(source.parentArtboard().objectID(),sncr.annotations.config.annotationParentKey,annotation);
454 |
455 | // Update annotations for source parent artboard
456 | sncr.annotations.updateAnnotations(context,source.parentArtboard().objectID());
457 |
458 | // Determine annotation name
459 | var annotationName = sncr.annotations.config.annotationLinkPrefix + annotation.name().split(/\r\n|\r|\n/g)[0].replace(sncr.annotations.config.annotationLinkPrefix,"");
460 |
461 | // Create a log event
462 | log(annotationName + sncr.strings["annotation-link-complete"] + source.name());
463 |
464 | // Display feedback
465 | sketch.UI.message(annotationName + sncr.strings["annotation-link-complete"] + source.name());
466 | },
467 | updateAnnotations: function(context,artboardID) {
468 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.annotationLinkKey + " != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.annotationLinkTypeKey + " == " + sncr.annotations.config.annotationLinkTypeValue,sncr.pluginDomain);
469 | var annotations = sncr.page.children().filteredArrayUsingPredicate(predicate);
470 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName);
471 | var noteGroup = getChildGroup(parentGroup,sncr.artboardNoteGroupName);
472 | var updateCount = 0;
473 | var removeCount = 0;
474 | var artboardsWithAnnotations = NSMutableArray.array();
475 |
476 | // If there are no annotations...
477 | if (annotations.count() == 0) {
478 | // Display feedback if the function was not invoked by an action...
479 | if (!context.actionContext) sketch.UI.message(updateCount + sncr.strings['annotation-update-complete']);
480 | }
481 |
482 | // Iterate through annotations...
483 | annotations.forEach(annotation => {
484 | if (debug) log('Processing annotation ' + annotation.name() + ' (' + annotation.objectID() + ')');
485 |
486 | // Get linked object
487 | var linkedObjectID = sncr.command.valueForKey_onLayer(sncr.annotations.config.annotationLinkKey,annotation);
488 | var linkedObject = sncr.data.layerWithID(linkedObjectID);
489 |
490 | var annotationPosition = sncr.command.valueForKey_onLayer('position',annotation);
491 | annotationPosition = (annotationPosition) ? annotationPosition : 0;
492 |
493 | // If linked object exists on current page...
494 | if (linkedObject && linkedObject.parentPage() == sncr.page) {
495 | var targetRect = linkedObject.absoluteRect().rect();
496 |
497 | // Get ID of parent artboard of linked object
498 | var artboardWithAnnotation = linkedObject.parentArtboard().objectID();
499 |
500 | // If no artboardID was passed, or if artboardID was passed and it matches ID of parent artboard of linked object
501 | if (!artboardID || artboardID == artboardWithAnnotation) {
502 | // Get the flowObjectID, if one exists (used if annotation links to a flow layer)
503 | var flowObjectID = sncr.command.valueForKey_onLayer_forPluginIdentifier('flowObjectID',annotation,sncr.pluginDomain);
504 |
505 | // If a flowObjectID exists...
506 | if (flowObjectID) {
507 | // If the flow object still exists...
508 | if (linkedObject.symbolMaster().layerWithID(flowObjectID)) {
509 | // Get destinationArtboardID in order to update linked annotation destination
510 | var destinationArtboardID = sketch.fromNative(linkedObject).overrides.filter(o => o.property === 'flowDestination' && o.path.includes(flowObjectID))[0].value;
511 |
512 | // Update annotation destination
513 | sncr.annotations.createAnnotation(linkedObject,destinationArtboardID,flowObjectID);
514 |
515 | // Determine annotation Y position
516 | var flowLayerRect = createNewRectForFlowLayer(linkedObject,flowObjectID);
517 | targetRect = flowLayerRect;
518 | var annotationY = flowLayerRect.origin.y + flowLayerRect.size.height/2 + sncr.annotations.config.annotationYOffset - sncr.annotations.config.annotationStyleData.lineHeight/2;
519 | }
520 | // Otherwise...
521 | else {
522 | // Remove stored values for linked artboard
523 | sncr.command.setValue_forKey_onLayer(nil,sncr.annotations.config.annotationLinkKey,annotation);
524 | sncr.command.setValue_forKey_onLayer(nil,sncr.annotations.config.annotationLinkTypeKey,annotation);
525 | sncr.command.setValue_forKey_onLayer(nil,sncr.annotations.config.annotationParentKey,annotation);
526 | sncr.command.setValue_forKey_onLayer(nil,'flowObjectID',annotation);
527 |
528 | // Create annotation name
529 | var annotationName = annotation.name().replace(sncr.annotations.config.annotationLinkPrefix,"");
530 |
531 | // Update annotation layer name
532 | annotation.setName(annotationName);
533 |
534 | // Iterate counters
535 | updateCount++;
536 | removeCount++;
537 |
538 | // Create a log event
539 | log(annotationName + sncr.strings["annotation-unlink-complete"] + linkedObjectID);
540 |
541 | return false;
542 | }
543 | }
544 | // If annotation links to an object...
545 | else {
546 | // If object has a flow destination...
547 | if (linkedObject.flow()) {
548 | // Get destinationArtboardID in order to update linked annotation destination
549 | var destinationArtboardID = linkedObject.flow().destinationArtboardID();
550 |
551 | // Update annotation destination
552 | sncr.annotations.createAnnotation(linkedObject,destinationArtboardID);
553 | }
554 |
555 | // Determine annotation Y position
556 | var annotationY = linkedObject.absoluteRect().y() + linkedObject.frame().height()/2 + sncr.annotations.config.annotationYOffset - sncr.annotations.config.annotationStyleData.lineHeight/2;
557 | }
558 |
559 | // Get max X of artboard, or parent artboard
560 | var artboardMaxX = CGRectGetMaxX(linkedObject.parentArtboard().rect());
561 | var artboardMidX = CGRectGetMidX(linkedObject.parentArtboard().rect());
562 | var artboardMinX = CGRectGetMinX(linkedObject.parentArtboard().rect());
563 |
564 | if (CGRectGetMidX(targetRect) < artboardMidX && annotation.class() != "MSSymbolInstance" && annotationPosition != 1) {
565 | // Update annotation x position (left of artboard)
566 | annotation.absoluteRect().setX(artboardMinX - sncr.annotations.config.annotationWidth - sncr.annotations.config.annotationXOffset);
567 | } else {
568 | // Update annotation x position (right of artboard)
569 | annotation.absoluteRect().setX(artboardMaxX + sncr.annotations.config.annotationXOffset);
570 | }
571 |
572 | // Update annotation y position
573 | annotation.absoluteRect().setY(annotationY);
574 |
575 | // Update annotation width
576 | annotation.frame().setWidth(sncr.annotations.config.annotationWidth);
577 |
578 | // If annotation is a text layer...
579 | if (annotation.class() == "MSTextLayer") {
580 | // Update annotation style
581 | annotation.setFontSize(sncr.annotations.config.annotationStyleData.fontSize);
582 | annotation.setLineHeight(sncr.annotations.config.annotationStyleData.lineHeight);
583 |
584 | if (CGRectGetMidX(targetRect) < artboardMidX && annotationPosition != 1) {
585 | annotation.setTextAlignment(1);
586 | } else {
587 | annotation.setTextAlignment(sncr.annotations.config.annotationStyleData.textAlignment);
588 | }
589 | }
590 |
591 | // If annotation is a symbol instance...
592 | if (annotation.class() == "MSSymbolInstance") {
593 | //log(`Working on ${annotation.name()}…`);
594 |
595 | // Get overrides in annotation...
596 | let overrides = sketch.fromNative(annotation).overrides.filter(o => o.editable);
597 |
598 | // Iterate through overrides...
599 | overrides.forEach(override => {
600 | if (override.property === 'flowDestination' && override.value) {
601 | let group = override.path.substr(0,override.path.indexOf('/'));
602 | let text = overrides.find(o => o.id.substr(0,o.id.indexOf('/')) == group && o.property == 'stringValue');
603 | let destination = sketch.getSelectedDocument().getLayerWithID(override.value);
604 |
605 | if (destination) {
606 | let name = destination.name.substr(0,destination.name.indexOf(' '));
607 |
608 | text.value = name;
609 | } else {
610 | // Display feedback
611 | sketch.UI.message(`There is a problematic hotspot override on the annotation for ${annotation.name()}…`);
612 | }
613 | }
614 | });
615 |
616 | // Adjust annotation size
617 | sketch.fromNative(annotation).resizeWithSmartLayout();
618 | }
619 |
620 | // Update annotation layer name
621 | annotation.setName(sncr.annotations.config.annotationLinkPrefix + linkedObject.name());
622 |
623 | // If the annotation is not in the annotation group...
624 | if (annotation.parentGroup() != noteGroup) {
625 | // Move the annotation to the annotation group
626 | annotation.moveToLayer_beforeLayer(noteGroup,nil);
627 | }
628 |
629 | // Select the annotation
630 | annotation.select_byExtendingSelection(1,1);
631 |
632 | // Move the annotation to the top of the annotation group
633 | MSLayerMovement.moveToFront([annotation]);
634 |
635 | // Deselect the annotation
636 | annotation.select_byExtendingSelection(0,1);
637 |
638 | // Iterate counter
639 | updateCount++;
640 |
641 | if (!artboardsWithAnnotations.containsObject(artboardWithAnnotation)) {
642 | artboardsWithAnnotations.addObject(artboardWithAnnotation);
643 | }
644 | }
645 |
646 | // Remove stored values no longer used
647 | sncr.command.setValue_forKey_onLayer(nil,sncr.annotations.config.annotationParentKey,annotation);
648 | }
649 | // If linked object does not exist...
650 | else {
651 | // Remove stored values for linked artboard
652 | sncr.command.setValue_forKey_onLayer(nil,sncr.annotations.config.annotationLinkKey,annotation);
653 | sncr.command.setValue_forKey_onLayer(nil,sncr.annotations.config.annotationLinkTypeKey,annotation);
654 | sncr.command.setValue_forKey_onLayer(nil,sncr.annotations.config.annotationParentKey,annotation);
655 | sncr.command.setValue_forKey_onLayer(nil,'flowObjectID',annotation);
656 |
657 | // Create annotation name
658 | var annotationName = annotation.name().replace(sncr.annotations.config.annotationLinkPrefix,"");
659 |
660 | // Update annotation layer name
661 | annotation.setName(annotationName);
662 |
663 | // Iterate counters
664 | updateCount++;
665 | removeCount++;
666 |
667 | // Create a log event
668 | log(annotationName + sncr.strings["annotation-unlink-complete"] + linkedObjectID);
669 | }
670 | });
671 |
672 | // If annotation group is not empty...
673 | if (noteGroup.layers().count() > 0) {
674 | artboardsWithAnnotations.forEach(artboardID => {
675 | var artboard = sncr.data.layerWithID(artboardID);
676 | var siblings = sncr.annotations.getAnnotations(artboardID);
677 |
678 | if (siblings.count() > 1) {
679 | var sortByX = NSSortDescriptor.sortDescriptorWithKey_ascending("absoluteRect.x",1);
680 | var sortByY = NSSortDescriptor.sortDescriptorWithKey_ascending("absoluteRect.y",1);
681 |
682 | siblings = siblings.sortedArrayUsingDescriptors([sortByX,sortByY]);
683 |
684 | // Iterate through the siblings...
685 | for (var i = 0; i < siblings.length; i++) {
686 | // If there is a next sibling...
687 | if (i + 1 < siblings.length) {
688 | // Sibling variables
689 | var thisSibling = siblings[i];
690 | var nextSibling = siblings[i+1];
691 |
692 | // If this sibling and the next are on the same vertical plane, and intersect...
693 | if (CGRectGetMinX(thisSibling.rect()) == CGRectGetMinX(nextSibling.rect()) && CGRectGetMaxY(thisSibling.rect()) + sncr.annotations.config.annotationSpacing >= CGRectGetMinY(nextSibling.rect())) {
694 | // Adjust the Y coordinate of the next sibling
695 | nextSibling.frame().setY(CGRectGetMaxY(thisSibling.rect()) + sncr.annotations.config.annotationSpacing);
696 | }
697 | }
698 | }
699 | }
700 | });
701 |
702 | // Resize annotation and parent groups to account for children
703 | if (sketch.version.sketch > 52) {
704 | noteGroup.fixGeometryWithOptions(0);
705 | parentGroup.fixGeometryWithOptions(0);
706 | } else {
707 | noteGroup.resizeToFitChildrenWithOption(0);
708 | parentGroup.resizeToFitChildrenWithOption(0);
709 | }
710 |
711 | // Redraw all connections
712 | sncr.annotations.updateConnections();
713 | }
714 | // If annotation group is empty...
715 | else {
716 | // Remove the annotation group
717 | noteGroup.removeFromParent();
718 |
719 | // Resize parent group to account for children
720 | if (sketch.version.sketch > 52) {
721 | parentGroup.fixGeometryWithOptions(0);
722 | } else {
723 | parentGroup.resizeToFitChildrenWithOption(0);
724 | }
725 | }
726 |
727 | // Move parent group to the top of the layer list
728 | parentGroup.moveToLayer_beforeLayer(sncr.page,nil);
729 |
730 | // Deselect parent group (moveToLayer_beforeLayer selects it)
731 | parentGroup.select_byExtendingSelection(0,1);
732 |
733 | // If the function was not invoked by action...
734 | if (!context.actionContext) {
735 | // If any annotation links were removed
736 | if (removeCount > 0) {
737 | // Display feedback
738 | sketch.UI.message(updateCount + sncr.strings["annotation-update-complete"] + ", " + removeCount + sncr.strings["annotation-update-complete-unlinked"]);
739 | } else {
740 | // Display feedback
741 | sketch.UI.message(updateCount + sncr.strings["annotation-update-complete"]);
742 | }
743 | }
744 | },
745 | updateConnections: function() {
746 | // Connections variables
747 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.connectionsGroupKey + " == true", sncr.pluginDomain);
748 | var connectionsGroup = sncr.page.children().filteredArrayUsingPredicate(predicate).firstObject();
749 |
750 | // Remove connections group if it exists...
751 | if (connectionsGroup) connectionsGroup.removeFromParent();
752 |
753 | // Create annotations loop
754 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.annotations.config.annotationLinkKey + " != nil", sncr.pluginDomain);
755 | var annotations = sncr.page.children().filteredArrayUsingPredicate(predicate);
756 |
757 | // Connections variable
758 | var connections = [];
759 |
760 | // Loop through annotations...
761 | annotations.forEach(function(annotation){
762 | // Get ID for linked object
763 | var linkedObjectID = sncr.command.valueForKey_onLayer_forPluginIdentifier(sncr.annotations.config.annotationLinkKey,annotation,sncr.pluginDomain);
764 |
765 | // Get linked object on current page
766 | var predicate = NSPredicate.predicateWithFormat("objectID == %@",linkedObjectID);
767 | var linkedObject = sncr.page.children().filteredArrayUsingPredicate(predicate).firstObject();
768 |
769 | // If linked object exists...
770 | if (linkedObject) {
771 | var flowObjectID = sncr.command.valueForKey_onLayer_forPluginIdentifier('flowObjectID',annotation,sncr.pluginDomain);
772 |
773 | if (linkedObject.class() == 'MSSymbolInstance' && flowObjectID) {
774 | // Create connection object
775 | var connection = {
776 | linkID : annotation.objectID(),
777 | linkRect : createNewRectForFlowLayer(linkedObject,flowObjectID),
778 | endPoint : {
779 | x : annotation.absoluteRect().x(),
780 | y : annotation.absoluteRect().y()
781 | }
782 | }
783 | } else {
784 | // Create connection object
785 | var connection = {
786 | linkID : annotation.objectID(),
787 | linkRect : linkedObject.parentArtboard() ? CGRectIntersection(linkedObject.absoluteRect().rect(),linkedObject.parentArtboard().absoluteRect().rect()) : linkedObject.absoluteRect().rect(),
788 | endPoint : {
789 | x : annotation.absoluteRect().x(),
790 | y : annotation.absoluteRect().y()
791 | }
792 | }
793 | }
794 |
795 | // Add connection object to connections array
796 | connections.push(connection);
797 | }
798 | });
799 |
800 | // Set parent group
801 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName);
802 |
803 | // Set annotation group
804 | var noteGroup = getChildGroup(parentGroup,sncr.artboardNoteGroupName);
805 |
806 | // Create new connections group
807 | connectionsGroup = MSLayerGroup.new();
808 |
809 | // Iterate through the connections...
810 | connections.forEach(function(connection,i){
811 | var linkRect = connection.linkRect;
812 | var startPoint;
813 | var startPointX;
814 | var startPointY;
815 | var endPoint;
816 | var endPointX;
817 | var endPointY;
818 |
819 | if (CGRectGetMinX(linkRect) < connection.endPoint.x) {
820 | startPointX = CGRectGetMaxX(linkRect) + sncr.annotations.config.annotationArrowStartXOffset;
821 | startPointY = CGRectGetMidY(linkRect) + sncr.annotations.config.annotationArrowStartYOffset;
822 | endPointX = connection.endPoint.x + sncr.annotations.config.annotationArrowEndXOffset;
823 | endPointY = connection.endPoint.y + sncr.annotations.config.annotationArrowEndYOffset;
824 | } else {
825 | startPointX = CGRectGetMinX(linkRect) - sncr.annotations.config.annotationArrowStartXOffset;
826 | startPointY = CGRectGetMidY(linkRect) + sncr.annotations.config.annotationArrowStartYOffset;
827 | endPointX = connection.endPoint.x + sncr.annotations.config.annotationWidth - sncr.annotations.config.annotationArrowEndXOffset;
828 | endPointY = connection.endPoint.y + sncr.annotations.config.annotationArrowEndYOffset;
829 | }
830 |
831 | startPoint = NSMakePoint(Math.round(startPointX),Math.round(startPointY));
832 | endPoint = NSMakePoint(Math.round(endPointX),Math.round(endPointY));
833 |
834 | var linePath = NSBezierPath.bezierPath();
835 | linePath.moveToPoint(startPoint);
836 |
837 | if (startPoint.y == endPoint.y) {
838 | linePath.lineToPoint(endPoint);
839 | } else {
840 | var controlPoint1Offset = Math.max(Math.abs(endPoint.x - startPoint.x)/2, 24);
841 | var controlPoint2Offset = Math.max(Math.abs(endPoint.x - startPoint.x)/2, 24);
842 | var controlPoint1 = NSMakePoint(startPoint.x + controlPoint1Offset, startPoint.y);
843 | var controlPoint2 = NSMakePoint(endPoint.x - controlPoint2Offset, endPoint.y);
844 |
845 | linePath.curveToPoint_controlPoint1_controlPoint2(endPoint,controlPoint1,controlPoint2);
846 | }
847 |
848 | var lineLayer = MSShapePathLayer.layerWithPath(MSPath.pathWithBezierPath(linePath));
849 | lineLayer.setName(sncr.annotations.config.annotationArrowName + ' ' + i);
850 | lineLayer.style().setStartMarkerType(sncr.annotations.config.annotationArrowStartType);
851 | lineLayer.style().setEndMarkerType(sncr.annotations.config.annotationArrowEndType);
852 |
853 | var lineStyle = lineLayer.style().addStylePartOfType(1);
854 | lineStyle.setColor(MSImmutableColor.colorWithSVGString(sncr.annotations.config.annotationArrowStrokeColor).newMutableCounterpart());
855 | lineStyle.setThickness(sncr.annotations.config.annotationArrowStrokeWidth);
856 | lineStyle.setPosition(0);
857 |
858 | connectionsGroup.addLayers([lineLayer]);
859 | });
860 |
861 | // Move connections group to annotation group
862 | connectionsGroup.moveToLayer_beforeLayer(noteGroup,nil);
863 |
864 | // Resize connections group to account for children
865 | if (sketch.version.sketch > 52) {
866 | connectionsGroup.fixGeometryWithOptions(0);
867 | } else {
868 | connectionsGroup.resizeToFitChildrenWithOption(0);
869 | }
870 |
871 | // Deselection connections and annotations groups
872 | connectionsGroup.deselectLayerAndParent();
873 |
874 | // Set connections group name
875 | connectionsGroup.setName(sncr.annotations.config.connectionsGroupName);
876 |
877 | // Lock connections group
878 | connectionsGroup.setIsLocked(1);
879 |
880 | // Set stored value on connections group
881 | sncr.command.setValue_forKey_onLayer_forPluginIdentifier(true,sncr.annotations.config.connectionsGroupKey,connectionsGroup,sncr.pluginDomain);
882 | },
883 | updatePosition: function() {
884 | // If nothing is selected...
885 | if (!sncr.selection) {
886 | sketch.UI.alert(sncr.strings["annotation-link-plugin"],'Select an annotation.');
887 | return;
888 | }
889 |
890 | var count = 0;
891 |
892 | sncr.selection.forEach(layer => {
893 | if (sncr.command.valueForKey_onLayer(sncr.annotations.config.annotationLinkTypeKey,layer)) {
894 | sncr.command.setValue_forKey_onLayer_forPluginIdentifier(1,'position',layer,sncr.pluginDomain);
895 | count++;
896 | }
897 | });
898 |
899 | // Display feedback
900 | sketch.UI.message(count + ' annotation(s) updated');
901 | },
902 | settings: function(context,command) {
903 | // Setting variables
904 | var defaultSettings = {};
905 | defaultSettings.autoAnnotate = 0;
906 |
907 | // Update default settings with cached settings
908 | defaultSettings = getCachedSettings(context,sncr.data,defaultSettings,sncr.pluginDomain);
909 |
910 | // If a command is not passed, operate in config mode...
911 | if (!command) {
912 | var alertWindow = COSAlertWindow.new();
913 |
914 | alertWindow.setMessageText(sncr.strings["annotation-settings"]);
915 |
916 | var autoAnnotate = createCheckbox({
917 | name : sncr.strings["annotation-settings-automatic"],
918 | value: 1
919 | },defaultSettings.autoAnnotate,NSMakeRect(0,0,300,54));
920 |
921 | alertWindow.addAccessoryView(autoAnnotate);
922 |
923 | var buttonOK = alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]);
924 | var buttonCancel = alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]);
925 |
926 | // Set key order and first responder
927 | setKeyOrder(alertWindow,[
928 | autoAnnotate,
929 | buttonOK
930 | ]);
931 |
932 | var responseCode = alertWindow.runModal();
933 |
934 | if (responseCode == 1000) {
935 | try {
936 | sncr.command.setValue_forKey_onLayer(autoAnnotate.state(),"autoAnnotate",sncr.data);
937 | }
938 | catch(err) {
939 | log(sncr.strings["general-save-failed"]);
940 | }
941 | } else return false;
942 | }
943 | // Otherwise operate in run mode...
944 | else {
945 | // Return updated settings
946 | return {
947 | autoAnnotate : defaultSettings.autoAnnotate
948 | }
949 | }
950 | }
951 | }
952 |
953 | sncr.common = {
954 | linkObject : function(layer,destination,type) {
955 | switch (type) {
956 | case "condition" :
957 | sncr.command.setValue_forKey_onLayer(type,"linkType",layer);
958 | sncr.command.setValue_forKey_onLayer(destination.objectID(),"linkedObject",layer);
959 | sncr.command.setValue_forKey_onLayer(destination.parentArtboard().objectID(),"linkedParentArtboard",layer);
960 |
961 | break;
962 | case "description" :
963 | sncr.command.setValue_forKey_onLayer(type,"linkType",layer);
964 | sncr.command.setValue_forKey_onLayer(destination,sncr.descriptions.config.descriptionLinkKey,layer);
965 |
966 | break;
967 | case "section" :
968 | sncr.command.setValue_forKey_onLayer(type,"linkType",layer);
969 | sncr.command.setValue_forKey_onLayer(destination,sncr.sections.config.titleLinkKey,layer);
970 |
971 | break;
972 | default :
973 | log("sncr.common.linkObject: Invalid or missing link type");
974 |
975 | break;
976 | }
977 | }
978 | }
979 |
980 | sncr.conditions = {
981 | addEdit: function(context) {
982 | var selection = sncr.selection.firstObject();
983 |
984 | if (sncr.selection.count() != 1 || sncr.selection.count() == 1 && selection.class() == "MSArtboardGroup") {
985 | sketch.UI.message("Select one layer which is not an artboard");
986 |
987 | return;
988 | }
989 |
990 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:','" + sncr.pluginDomain + "').linkType == 'condition' && function(userInfo,'valueForKeyPath:','" + sncr.pluginDomain + "').linkedObject == '" + selection.objectID() + "'"),
991 | conditionGroup = sncr.page.artboards().filteredArrayUsingPredicate(predicate).firstObject();
992 |
993 | if (!conditionGroup) {
994 | var conditionGroup = MSArtboardGroup.new(),
995 | insertIndex = getLayerIndex(selection.parentArtboard()) + 1;
996 |
997 | sncr.common.linkObject(conditionGroup,selection,"condition");
998 | selection.parentArtboard().parentGroup().insertLayer_atIndex(conditionGroup,insertIndex);
999 | }
1000 |
1001 | conditionGroup.setName("-Conditions-");
1002 | conditionGroup.setHasBackgroundColor(1);
1003 | conditionGroup.setBackgroundColor(MSImmutableColor.colorWithSVGString("#F2F2F2"));
1004 |
1005 | sncr.layout.preclude(conditionGroup);
1006 |
1007 | conditionGroup.absoluteRect().setX(CGRectGetMaxX(selection.parentArtboard().rect()) + sncr.annotations.config.annotationXOffset);
1008 | conditionGroup.absoluteRect().setY(selection.absoluteRect().y() + selection.frame().height() / 2 - 16);
1009 |
1010 | var predicate = NSPredicate.predicateWithFormat("className == %@","MSTextLayer"),
1011 | conditions = conditionGroup.children().filteredArrayUsingPredicate(predicate);
1012 |
1013 | var alertWindow = COSAlertWindow.new();
1014 | alertWindow.setMessageText("Conditions for " + selection.name());
1015 |
1016 | if (conditions.count() > 0) {
1017 | for (var i = 0; i < conditions.count(); i++) {
1018 | alertWindow.addAccessoryView(createLabel("Condition " + (i + 1),NSMakeRect(0,0,160,16)));
1019 | alertWindow.addAccessoryView(createField(conditions[i].stringValue(),NSMakeRect(0,0,300,20)));
1020 | }
1021 | }
1022 |
1023 | alertWindow.addAccessoryView(createLabel("New Condition ",NSMakeRect(0,0,160,16)));
1024 | alertWindow.addAccessoryView(createField("",NSMakeRect(0,0,300,20)));
1025 |
1026 | alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]);
1027 | alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]);
1028 | alertWindow.addButtonWithTitle("Add Condition");
1029 |
1030 | var responseCode = alertWindow.runModal();
1031 |
1032 | if (responseCode == 1000) {
1033 | // Do something
1034 | } else if (responseCode == 1002) {
1035 | // Add new condition to dialog
1036 | } else return false;
1037 |
1038 | // Add in condition groups/texts
1039 | // Resize to fit
1040 |
1041 | conditionGroup.frame().setWidth(200);
1042 | conditionGroup.frame().setHeight(200);
1043 | }
1044 | }
1045 |
1046 | sncr.descriptions = {
1047 | addEdit : function(context) {
1048 | // If there is one artboard selected...
1049 | if (sncr.selection.count() == 1 && (sncr.selection[0] instanceof MSArtboardGroup || sncr.selection[0] instanceof MSSymbolMaster)) {
1050 | // Artboard variable
1051 | var artboard = sncr.selection[0];
1052 |
1053 | // Get existing artboard description for selected artboard
1054 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.descriptions.config.descriptionLinkKey + " == '" + artboard.objectID() + "'",sncr.pluginDomain),
1055 | linkedDescription = sncr.page.children().filteredArrayUsingPredicate(predicate).firstObject();
1056 |
1057 | // Determine the initial artboard description value
1058 | var artboardDescription = (linkedDescription) ? linkedDescription.stringValue() : "";
1059 |
1060 | // Present add/edit window with artboard description value
1061 | artboardDescription = getArtboardDescription(artboard.name(),artboardDescription);
1062 |
1063 | // If artboard description value was returned
1064 | if (artboardDescription) {
1065 | // If artboard description already existed...
1066 | if (linkedDescription) {
1067 | // Update the artboard description with new value
1068 | linkedDescription.setStringValue(artboardDescription);
1069 |
1070 | // Display feedback
1071 | sketch.UI.message(sncr.strings["description-update-complete"]);
1072 | }
1073 | // If artboard description did not exist...
1074 | else {
1075 | // Set parent group
1076 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName);
1077 |
1078 | // Set annotation group
1079 | var descGroup = getChildGroup(parentGroup,sncr.descriptionsGroupName);
1080 |
1081 | // Set artboard description style
1082 | var artboardDescStyle = getTextStyle(sncr.descriptions.config.descriptionStyleName,sncr.descriptions.config.descriptionStyleData);
1083 |
1084 | // Create new artboard description text layer
1085 | var artboardDesc = MSTextLayer.new();
1086 | artboardDesc.setStringValue(artboardDescription);
1087 | artboardDesc.setName(sncr.descriptions.config.descriptionLinkPrefix + artboard.name());
1088 | artboardDesc.setTextBehaviour(0);
1089 |
1090 | // Apply style to artboard description
1091 | if (artboardDescStyle.newInstance) {
1092 | artboardDesc.setStyle(artboardDescStyle.newInstance());
1093 | } else {
1094 | artboardDesc.setSharedStyle(artboardDescStyle);
1095 | }
1096 |
1097 | // Add artboard description to annotation group
1098 | descGroup.addLayers([artboardDesc]);
1099 |
1100 | // Set artboard description x/y in relation to artboard, with offsets
1101 | artboardDesc.absoluteRect().setX(artboard.frame().x() + sncr.descriptions.config.descriptionXOffset);
1102 | artboardDesc.absoluteRect().setY(artboard.frame().y() + artboard.frame().height() + sncr.descriptions.config.descriptionYOffset);
1103 |
1104 | // Set artboard description width
1105 | artboardDesc.frame().setWidth(artboard.frame().width());
1106 | artboardDesc.setTextBehaviour(1);
1107 |
1108 | // Resize description and parent groups to account for children
1109 | if (sketch.version.sketch > 52) {
1110 | descGroup.fixGeometryWithOptions(0);
1111 | parentGroup.fixGeometryWithOptions(0);
1112 | } else {
1113 | descGroup.resizeToFitChildrenWithOption(0);
1114 | parentGroup.resizeToFitChildrenWithOption(0);
1115 | }
1116 |
1117 | // Set stored value for linked artboard
1118 | sncr.command.setValue_forKey_onLayer(artboard.objectID(),sncr.descriptions.config.descriptionLinkKey,artboardDesc);
1119 |
1120 | // Display feedback
1121 | sketch.UI.message(sncr.strings["description-set-complete"]);
1122 | }
1123 | }
1124 |
1125 | function getArtboardDescription(artboardName,descriptionVal) {
1126 | var alertWindow = COSAlertWindow.new();
1127 |
1128 | alertWindow.setMessageText(sncr.strings["description-set-plugin"]);
1129 |
1130 | alertWindow.addTextLabelWithValue("For " + artboardName + ":");
1131 |
1132 | var descriptionText = createField(descriptionVal,NSMakeRect(0,0,300,120));
1133 | alertWindow.addAccessoryView(descriptionText);
1134 |
1135 | var buttonOK = alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]);
1136 | var buttonCancel = alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]);
1137 |
1138 | setKeyOrder(alertWindow,[
1139 | descriptionText,
1140 | buttonOK
1141 | ]);
1142 |
1143 | var responseCode = alertWindow.runModal();
1144 |
1145 | if (responseCode == 1000) {
1146 | return descriptionText.stringValue();
1147 | } else return false;
1148 | }
1149 | }
1150 | // If there is not one artboard selected...
1151 | else {
1152 | // Display feedback
1153 | sketch.UI.alert(sncr.strings["description-set-plugin"],sncr.strings["description-set-problem"]);
1154 | }
1155 | },
1156 | linkSelected : function(context) {
1157 | // Validate the selections to link
1158 | var selections = sncr.descriptions.validateSelected(context);
1159 |
1160 | // If selections are valid...
1161 | if (selections) {
1162 | // Set stored value for linked artboard
1163 | sncr.common.linkObject(selections.description,selections.artboard.objectID(),"description");
1164 |
1165 | // Set parent group
1166 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName);
1167 |
1168 | // Set annotation group
1169 | var descGroup = getChildGroup(parentGroup,sncr.descriptionsGroupName);
1170 |
1171 | // Set artboard description x/y in relation to artboard, with offsets
1172 | selections.description.absoluteRect().setX(selections.artboard.frame().x() + sncr.descriptions.config.descriptionXOffset);
1173 | selections.description.absoluteRect().setY(selections.artboard.frame().y() + selections.artboard.frame().height() + sncr.descriptions.config.descriptionYOffset);
1174 |
1175 | // Set artboard description text behavior
1176 | selections.description.setTextBehaviour(0);
1177 |
1178 | // Set artboard description width
1179 | selections.description.frame().setWidth(selections.artboard.frame().width());
1180 | selections.description.setTextBehaviour(1);
1181 |
1182 | // If the artboard description is not in the description group...
1183 | if (selections.description.parentGroup() != descGroup) {
1184 | // Move the artboard description to the description group
1185 | selections.description.moveToLayer_beforeLayer(descGroup,nil);
1186 |
1187 | // Deselect the artboard description (moveToLayer_beforeLayer selects it)
1188 | selections.description.select_byExtendingSelection(0,1);
1189 | }
1190 |
1191 | // Deselect the artboard
1192 | selections.artboard.select_byExtendingSelection(0,1);
1193 |
1194 | // Resize description and parent groups to account for children
1195 | if (sketch.version.sketch > 52) {
1196 | descGroup.fixGeometryWithOptions(0);
1197 | parentGroup.fixGeometryWithOptions(0);
1198 | } else {
1199 | descGroup.resizeToFitChildrenWithOption(0);
1200 | parentGroup.resizeToFitChildrenWithOption(0);
1201 | }
1202 |
1203 | // Set layer name
1204 | var layerName = sncr.descriptions.config.descriptionLinkPrefix + selections.artboard.name();
1205 |
1206 | // Update the layer name
1207 | selections.description.setName(layerName);
1208 |
1209 | // Create a log event
1210 | log(layerName + sncr.strings["description-link-complete"] + selections.artboard.name());
1211 |
1212 | // Display feedback
1213 | sketch.UI.message(layerName + sncr.strings["description-link-complete"] + selections.artboard.name());
1214 | }
1215 | },
1216 | unlinkSelected : function(context) {
1217 | // If there are selections...
1218 | if (sncr.selection.count() > 0) {
1219 | // Set a counter
1220 | var count = 0;
1221 |
1222 | // Iterate through selections...
1223 | for (var i = 0; i < sncr.selection.count(); i++) {
1224 | // Get stored value for linked artboard
1225 | var linkedArtboard = sncr.command.valueForKey_onLayer(sncr.descriptions.config.descriptionLinkKey,sncr.selection[i]);
1226 |
1227 | // If selection is linked to an artboard...
1228 | if (linkedArtboard) {
1229 | // Set linked artboard value to nil
1230 | sncr.command.setValue_forKey_onLayer(nil,sncr.descriptions.config.descriptionLinkKey,sncr.selection[i]);
1231 |
1232 | // Set the layer name
1233 | var layerName = sncr.selection[i].name().replace(sncr.descriptions.config.descriptionLinkPrefix,""));
1234 |
1235 | // Update the title name
1236 | sncr.selection[i].setName(layerName);
1237 |
1238 | // For logging purposes, get linked artboard object
1239 | var artboard = findLayerByID(sncr.selection[i].parentGroup(),linkedArtboard);
1240 |
1241 | // If artboard exists, use artboard name for name, otherwise use artboard ID
1242 | artboardName = (artboard) ? artboard.name() : linkedArtboard;
1243 |
1244 | // Create a log event
1245 | log(layerName + sncr.strings["description-unlink-complete"] + artboardName);
1246 |
1247 | // Iterate the counter
1248 | count++;
1249 | }
1250 |
1251 | // Deselect current selection
1252 | sncr.selection[i].select_byExtendingSelection(0,1);
1253 | }
1254 |
1255 | // Display feedback
1256 | sketch.UI.message(count + sncr.strings["description-unlinks-complete"]);
1257 | }
1258 | // If there are no selections...
1259 | else {
1260 | // Display feedback
1261 | sketch.UI.alert(sncr.strings["description-unlink-plugin"],sncr.strings["description-unlink-problem"]);
1262 | }
1263 | },
1264 | updateAllOnPage: function(context,command) {
1265 | // If function was invoked by action, set command
1266 | if (!command && context.actionContext) command = "action";
1267 |
1268 | logFunctionStart("Artboard Descriptions: Update",command);
1269 |
1270 | var settings = sncr.descriptions.settings(context,"update");
1271 |
1272 | // Get descriptions on current page
1273 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.descriptions.config.descriptionLinkKey + " != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.descriptions.config.descriptionLinkTypeKey + " == nil",sncr.pluginDomain),
1274 | layers = sncr.page.children().filteredArrayUsingPredicate(predicate),
1275 | loop = layers.objectEnumerator(),
1276 | layer;
1277 |
1278 | // If there are descriptions...
1279 | if (layers.count() > 0) {
1280 | // Set counters
1281 | var updateCount = 0;
1282 | var removeCount = 0;
1283 |
1284 | // Set parent group
1285 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName);
1286 |
1287 | // Set annotation group
1288 | var descGroup = getChildGroup(parentGroup,sncr.descriptionsGroupName);
1289 |
1290 | // Iterate through descriptions...
1291 | while (layer = loop.nextObject()) {
1292 | sncr.command.setValue_forKey_onLayer(sncr.descriptions.config.descriptionLinkTypeValue,sncr.descriptions.config.descriptionLinkTypeKey,layer);
1293 |
1294 | // Get stored value for linked artboard
1295 | var linkedArtboard = sncr.command.valueForKey_onLayer(sncr.descriptions.config.descriptionLinkKey,layer);
1296 |
1297 | // Get linked artboard object, if it resides on the artboard description page
1298 | var predicate = NSPredicate.predicateWithFormat("objectID == %@",linkedArtboard,sncr.pluginDomain),
1299 | artboard = sncr.page.artboards().filteredArrayUsingPredicate(predicate).firstObject();
1300 |
1301 | // If artboard object exists...
1302 | if (artboard) {
1303 | // Determine layer width
1304 | var layerWidth = (settings.descriptionWidth && settings.descriptionWidth != "") ? settings.descriptionWidth : artboard.frame().width();
1305 |
1306 | // Set artboard description text behavior
1307 | layer.setTextBehaviour(0);
1308 |
1309 | // Set artboard description width
1310 | layer.frame().setWidth(layerWidth);
1311 | layer.setTextBehaviour(1);
1312 |
1313 | // Set artboard description x/y in relation to artboard, with offsets
1314 | if (settings.descriptionPosition == 1) { // Right
1315 | layer.absoluteRect().setX(artboard.frame().x() + artboard.frame().width() + settings.descriptionXOffset);
1316 | layer.absoluteRect().setY(artboard.frame().y() + settings.descriptionYOffset);
1317 | } else if (settings.descriptionPosition == 2) { // Bottom
1318 | layer.absoluteRect().setX(artboard.frame().x() + settings.descriptionXOffset);
1319 | layer.absoluteRect().setY(artboard.frame().y() + artboard.frame().height() + settings.descriptionYOffset);
1320 | } else if (settings.descriptionPosition == 3) { // Left
1321 | layer.absoluteRect().setX(artboard.frame().x() + settings.descriptionXOffset - layerWidth);
1322 | layer.absoluteRect().setY(artboard.frame().y() + settings.descriptionYOffset);
1323 | } else { // Top
1324 | layer.absoluteRect().setX(artboard.frame().x() + settings.descriptionXOffset);
1325 | layer.absoluteRect().setY(artboard.frame().y() + settings.descriptionYOffset - layer.frame().height());
1326 | }
1327 |
1328 | // If the artboard description is not in the description group...
1329 | if (layer.parentGroup() != descGroup) {
1330 | // Move the artboard description to the description group
1331 | layer.moveToLayer_beforeLayer(descGroup,nil);
1332 |
1333 | // Deselect the artboard description (moveToLayer_beforeLayer selects it)
1334 | layer.select_byExtendingSelection(0,1);
1335 | }
1336 |
1337 | // Set layer name
1338 | var layerName = sncr.descriptions.config.descriptionLinkPrefix + artboard.name();
1339 |
1340 | // Update the layer name
1341 | layer.setName(layerName);
1342 |
1343 | // Iterate counter
1344 | updateCount++;
1345 | }
1346 | // If artboard object does not exist...
1347 | else {
1348 | // Remove stored value for linked artboard
1349 | sncr.command.setValue_forKey_onLayer(nil,sncr.descriptions.config.descriptionLinkKey,layer);
1350 |
1351 | // Set layer name
1352 | var layerName = layer.name().replace(sncr.descriptions.config.descriptionLinkPrefix,""));
1353 |
1354 | // Update the layer name
1355 | layer.setName(layerName);
1356 |
1357 | // Create a log event
1358 | log(layerName + sncr.strings["description-unlink-complete"] + linkedArtboard);
1359 |
1360 | // Iterate counters
1361 | updateCount++;
1362 | removeCount++;
1363 | }
1364 | }
1365 |
1366 | // If description group is not empty...
1367 | if (descGroup.layers().count() > 0) {
1368 | // Resize description group to account for children
1369 | if (sketch.version.sketch > 52) {
1370 | descGroup.fixGeometryWithOptions(0);
1371 | } else {
1372 | descGroup.resizeToFitChildrenWithOption(0);
1373 | }
1374 | }
1375 | // If description group is empty...
1376 | else {
1377 | // Remove the description group
1378 | descGroup.removeFromParent();
1379 | }
1380 |
1381 | // Resize parent group to account for children
1382 | if (sketch.version.sketch > 52) {
1383 | parentGroup.fixGeometryWithOptions(0);
1384 | } else {
1385 | parentGroup.resizeToFitChildrenWithOption(0);
1386 | }
1387 |
1388 | // Move parent group to the top of the layer list
1389 | parentGroup.moveToLayer_beforeLayer(sncr.page,nil);
1390 |
1391 | // Deselect parent group
1392 | parentGroup.select_byExtendingSelection(0,1);
1393 |
1394 | // If the function was not invoked by action...
1395 | if (!context.actionContext) {
1396 | // Lock the parent group
1397 | parentGroup.setIsLocked(1);
1398 |
1399 | // If any artboard links were removed
1400 | if (removeCount > 0) {
1401 | // Display feedback
1402 | sketch.UI.message(updateCount + sncr.strings["description-updates-complete"] + ", " + removeCount + sncr.strings["description-updates-complete-unlinked"]);
1403 | } else {
1404 | // Display feedback
1405 | sketch.UI.message(updateCount + sncr.strings["description-updates-complete"]);
1406 | }
1407 | }
1408 | }
1409 | },
1410 | settings : function(context,command) {
1411 | var descriptionPositions = ["Top","Right","Bottom","Left"];
1412 |
1413 | // Setting variables
1414 | var settings = {};
1415 | settings.descriptionWidth = "";
1416 | settings.descriptionPosition = 2;
1417 | settings.descriptionXOffset = sncr.descriptions.config.descriptionXOffset;
1418 | settings.descriptionYOffset = sncr.descriptions.config.descriptionYOffset;
1419 |
1420 | settings = getCachedSettings(context,sncr.data,settings,sncr.pluginDomain);
1421 |
1422 | if (!command) {
1423 | var alertWindow = COSAlertWindow.new();
1424 | alertWindow.setMessageText(sncr.strings["description-settings-title"]);
1425 |
1426 | alertWindow.addTextLabelWithValue(sncr.strings["description-settings-width"]);
1427 |
1428 | var descriptionWidth = createField(settings.descriptionWidth,NSMakeRect(0,0,60,24));
1429 | alertWindow.addAccessoryView(descriptionWidth);
1430 |
1431 | alertWindow.addTextLabelWithValue(sncr.strings["description-settings-position"]);
1432 |
1433 | var descriptionPosition = createSelect(descriptionPositions,settings.descriptionPosition,NSMakeRect(0,0,80,28));
1434 | alertWindow.addAccessoryView(descriptionPosition);
1435 |
1436 | alertWindow.addTextLabelWithValue(sncr.strings["description-settings-offsetX"]);
1437 |
1438 | var descriptionXOffset = createField(settings.descriptionXOffset,NSMakeRect(0,0,60,24));
1439 | alertWindow.addAccessoryView(descriptionXOffset);
1440 |
1441 | alertWindow.addTextLabelWithValue(sncr.strings["description-settings-offsetY"]);
1442 |
1443 | var descriptionYOffset = createField(settings.descriptionYOffset,NSMakeRect(0,0,60,24));
1444 | alertWindow.addAccessoryView(descriptionYOffset);
1445 |
1446 | var buttonOK = alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]);
1447 | var buttonCancel = alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]);
1448 |
1449 | setKeyOrder(alertWindow,[
1450 | descriptionWidth,
1451 | descriptionPosition,
1452 | descriptionXOffset,
1453 | descriptionYOffset,
1454 | buttonOK
1455 | ]);
1456 |
1457 | var responseCode = alertWindow.runModal();
1458 |
1459 | if (responseCode == 1000) {
1460 | try {
1461 | sncr.command.setValue_forKey_onLayer(descriptionWidth.stringValue(),"descriptionWidth",sncr.data);
1462 | sncr.command.setValue_forKey_onLayer(descriptionPosition.indexOfSelectedItem(),"descriptionPosition",sncr.data);
1463 | sncr.command.setValue_forKey_onLayer(Number(descriptionXOffset.stringValue()),"descriptionXOffset",sncr.data);
1464 | sncr.command.setValue_forKey_onLayer(Number(descriptionYOffset.stringValue()),"descriptionYOffset",sncr.data);
1465 | }
1466 | catch(err) {
1467 | log(sncr.strings["general-save-failed"]);
1468 | }
1469 |
1470 | sncr.descriptions.updateAllOnPage(context,"settings");
1471 | } else return false;
1472 | } else {
1473 | return {
1474 | descriptionWidth : settings.descriptionWidth,
1475 | descriptionPosition : settings.descriptionPosition,
1476 | descriptionXOffset : settings.descriptionXOffset,
1477 | descriptionYOffset : settings.descriptionYOffset
1478 | }
1479 | }
1480 | },
1481 | validateSelected: function(context) {
1482 | // Get latest selections, as they may have been changed by Insert
1483 | var selections = sncr.page.selectedLayers().layers();
1484 |
1485 | // If two objects are not selected...
1486 | if (selections.count() != 2) {
1487 | // Display feedback
1488 | sketch.UI.alert(sncr.strings["description-link-plugin"],sncr.strings["description-link-problem"]);
1489 |
1490 | return false;
1491 | }
1492 |
1493 | // Selection variables
1494 | var firstObject = selections.firstObject(),
1495 | lastObject = selections.lastObject();
1496 |
1497 | // If the first item is not an artboard and the second item is an artboard...
1498 | if ((firstObject.class() != "MSArtboardGroup" || firstObject.class() != "MSSymbolMaster") && (lastObject.class() == "MSArtboardGroup" || lastObject.class() == "MSSymbolMaster")) {
1499 | return {
1500 | description : firstObject,
1501 | artboard : lastObject
1502 | }
1503 | }
1504 | // If the first item is an artboard and the second item is not an artboard
1505 | else if ((firstObject.class() == "MSArtboardGroup" || firstObject.class() == "MSSymbolMaster") && (lastObject.class() != "MSArtboardGroup" || lastObject.class() != "MSSymbolMaster")) {
1506 | return {
1507 | description : lastObject,
1508 | artboard : firstObject
1509 | }
1510 | }
1511 | // If the selections are two artboards...
1512 | else {
1513 | // Display feedback
1514 | sketch.UI.alert(sncr.strings["description-link-plugin"],sncr.strings["description-link-problem"]);
1515 |
1516 | return false;
1517 | }
1518 | }
1519 | }
1520 |
1521 | sncr.layout = {
1522 | update: function(context) {
1523 | if (!context.actionContext) {
1524 | sncr.layout.includePage(context,false);
1525 | } else {
1526 | sncr.layout.sanitizePages(context);
1527 | }
1528 |
1529 | if (sncr.command.valueForKey_onLayer_forPluginIdentifier(sncr.layout.config.featureKey,sncr.page,sncr.pluginDomain) != false) {
1530 | // Get artboards to layout
1531 | var predicate = NSPredicate.predicateWithFormat("userInfo == nil || function(userInfo,'valueForKeyPath:',%@)." + sncr.layout.config.featureKey + " != " + false,sncr.pluginDomain),
1532 | artboards = sncr.page.artboards().filteredArrayUsingPredicate(predicate),
1533 | artboardLoop = artboards.objectEnumerator(),
1534 | artboard;
1535 |
1536 | // If there artboards to layout...
1537 | if (artboards.count()) {
1538 | // Reset page origin
1539 | sncr.page.setRulerBase(CGPointMake(0,0));
1540 |
1541 | // Get layout settings
1542 | var layoutSettings = sncr.layout.settings(context,"update");
1543 |
1544 | // If artboards should be sorted...
1545 | if (layoutSettings.sortOrder != 0) {
1546 | var sortByName = NSSortDescriptor.sortDescriptorWithKey_ascending("name",1),
1547 | artboards = artboards.sortedArrayUsingDescriptors([sortByName]);
1548 |
1549 | var layoutLayers = (layoutSettings.sortOrder == 2) ? artboards.reverseObjectEnumerator().allObjects() : artboards;
1550 |
1551 | sortLayerList(layoutLayers,sncr.page);
1552 | }
1553 |
1554 | var firstBoard = artboards.objectAtIndex(0),
1555 | lastBoard = artboards.objectAtIndex(artboards.count() - 1),
1556 | lastBoardPrefix = 0,
1557 | groupType = parseInt(firstBoard.name()) == parseInt(lastBoard.name()) ? 0 : 1,
1558 | groupCount = 1,
1559 | groupLayout = [];
1560 |
1561 | while (artboard = artboardLoop.nextObject()) {
1562 | var artboardName = artboard.name(),
1563 | thisBoardPrefix = (groupType == 0) ? parseFloat(artboardName) : parseInt(artboardName);
1564 |
1565 | if (lastBoardPrefix != 0 && lastBoardPrefix != thisBoardPrefix) {
1566 | groupCount++;
1567 | }
1568 |
1569 | groupLayout.push({
1570 | artboard : artboardName,
1571 | prefix : thisBoardPrefix,
1572 | group : groupCount,
1573 | width : artboard.frame().width()
1574 | });
1575 |
1576 | lastBoardPrefix = thisBoardPrefix;
1577 | }
1578 |
1579 | var x = 0,
1580 | y = 0,
1581 | xPad = parseInt(layoutSettings.xPad),
1582 | yPad = parseInt(layoutSettings.yPad),
1583 | xCount = 0,
1584 | rowCount = layoutSettings.rowCount,
1585 | rowHeight = 0,
1586 | groupCount = 1;
1587 |
1588 | for (var i = 0; i < groupLayout.length; i++) {
1589 | var artboard = artboards.objectAtIndex(i),
1590 | artboardFrame = artboard.frame();
1591 |
1592 | // If starting a new group, reset x and calculate the y position of the next row
1593 | if (groupLayout[i]['group'] != groupCount) {
1594 | var nextGroupTotal = groupCounter(groupCount + 1,groupLayout);
1595 |
1596 | if (parseInt(layoutSettings.fitWidth)) {
1597 | if (layoutSettings.rowDensity == 1 || (rowCount - (xCount + 1)) < nextGroupTotal) {
1598 | x = 0;
1599 | y += rowHeight + yPad;
1600 | rowHeight = 0;
1601 | xCount = 0;
1602 | } else {
1603 | x += artboardFrame.width() + xPad;
1604 | xCount++;
1605 | }
1606 | } else {
1607 | if (layoutSettings.rowDensity == 1 || (rowCount - (xCount + 1)) < nextGroupTotal) {
1608 | x = 0;
1609 | y += rowHeight + yPad;
1610 | rowHeight = 0;
1611 | xCount = 0;
1612 | } else {
1613 | x += artboardFrame.width() + xPad;
1614 | xCount++;
1615 | }
1616 | }
1617 |
1618 | groupCount++;
1619 | }
1620 |
1621 | // If new line is detected but is continuation of group, give smaller vertical padding
1622 | if (x == 0 && xCount != 0) {
1623 | y += yPad / 2;
1624 | }
1625 |
1626 | // Position current artboard
1627 | artboardFrame.x = x;
1628 | artboardFrame.y = y;
1629 |
1630 | // Keep track if this artboard is taller than previous artboards in row
1631 | if (artboardFrame.height() > rowHeight) {
1632 | rowHeight = artboardFrame.height();
1633 | }
1634 |
1635 | if (parseInt(layoutSettings.fitWidth)) {
1636 | if (x + artboardFrame.width() + xPad * 2 > layoutSettings.fitWidth) {
1637 | x = 0;
1638 | y += rowHeight;
1639 | rowHeight = 0;
1640 | } else {
1641 | x += artboardFrame.width() + xPad;
1642 | }
1643 | } else {
1644 | // Determine if this is the last artboard the row, reset x and calculate the y position of the next row
1645 | if ((xCount + 1) % rowCount == 0) {
1646 | x = 0;
1647 | y += rowHeight;
1648 | rowHeight = 0;
1649 | } else {
1650 | x += artboardFrame.width() + xPad;
1651 | }
1652 | }
1653 |
1654 | xCount++;
1655 | }
1656 |
1657 | if (layoutSettings.autoSections) sncr.sections.updateAllOnPage(context,"layout");
1658 | if (layoutSettings.autoTitles) sncr.titles.create(context,"layout");
1659 | if (layoutSettings.autoDescriptions) sncr.descriptions.updateAllOnPage(context,"layout");
1660 | if (layoutSettings.autoAnnotations) sncr.annotations.updateAnnotations(context);
1661 |
1662 | // Collapse everything if run manually
1663 | if (!context.actionContext) actionWithType(context,"MSCollapseAllGroupsAction").doPerformAction(nil);
1664 |
1665 | // Feedback to user
1666 | sketch.UI.message(sncr.strings["layout-artboards-complete"]);
1667 | }
1668 |
1669 | function groupCounter(group,obj) {
1670 | var count = 0;
1671 |
1672 | for (var i = 0; i < obj.length; ++i) {
1673 | if (obj[i]['group'] == group) {
1674 | count++;
1675 | }
1676 | }
1677 |
1678 | return count;
1679 | }
1680 |
1681 | function sortLayerList(artboards,output) {
1682 | var loop = artboards.objectEnumerator(), artboard;
1683 |
1684 | while (artboard = loop.nextObject()) {
1685 | artboard.moveToLayer_beforeLayer(output,nil);
1686 | artboard.select_byExtendingSelection(0,1);
1687 | }
1688 | }
1689 | }
1690 | },
1691 | includeSelected: function(context) {
1692 | var count = 0;
1693 |
1694 | if (sncr.selection.count()) {
1695 | for (var i = 0; i < sncr.selection.count(); i++) {
1696 | if (sncr.selection[i] instanceof MSArtboardGroup) {
1697 | sncr.layout.include(sncr.selection[i]);
1698 |
1699 | count++;
1700 |
1701 | log(sncr.selection[i].name() + sncr.strings["layout-include-complete"]);
1702 | }
1703 | }
1704 |
1705 | if (sncr.selection.count() == 1) {
1706 | sketch.UI.message(sncr.selection[0].name() + sncr.strings["layout-include-complete"]);
1707 | } else {
1708 | sketch.UI.message(count + sncr.strings["layout-includes-complete"]);
1709 | }
1710 | } else {
1711 | sketch.UI.alert(sncr.strings["layout-include-plugin"],sncr.strings["layout-include-problem"]);
1712 | }
1713 | },
1714 | include: function(object) {
1715 | sncr.command.setValue_forKey_onLayer(true,sncr.layout.config.featureKey,object);
1716 | },
1717 | precludeSelected: function(context) {
1718 | var count = 0;
1719 |
1720 | if (sncr.selection.count()) {
1721 | for (var i = 0; i < sncr.selection.count(); i++) {
1722 | if (sncr.selection[i] instanceof MSArtboardGroup) {
1723 | sncr.layout.preclude(sncr.selection[i]);
1724 |
1725 | count++;
1726 |
1727 | log(sncr.selection[i].name() + sncr.strings["layout-preclude-complete"]);
1728 | }
1729 | }
1730 |
1731 | if (sncr.selection.count() == 1) {
1732 | sketch.UI.message(sncr.selection[0].name() + sncr.strings["layout-preclude-complete"]);
1733 | } else {
1734 | sketch.UI.message(count + sncr.strings["layout-precludes-complete"]);
1735 | }
1736 | } else {
1737 | sketch.UI.alert(sncr.strings["layout-preclude-plugin"],sncr.strings["layout-preclude-problem"]);
1738 | }
1739 | },
1740 | preclude: function(object) {
1741 | sncr.command.setValue_forKey_onLayer(false,sncr.layout.config.featureKey,object);
1742 | },
1743 | includePage: function(context,feedback) {
1744 | sncr.layout.include(sncr.page);
1745 |
1746 | sncr.layout.sanitizePages(context);
1747 |
1748 | if (!feedback) {
1749 | sketch.UI.message(sncr.page.name() + sncr.strings["layout-include-page-complete"]);
1750 | }
1751 | },
1752 | precludePage: function(context) {
1753 | sncr.layout.preclude(sncr.page);
1754 |
1755 | sncr.layout.sanitizePages(context);
1756 |
1757 | sketch.UI.message(sncr.page.name() + sncr.strings["layout-preclude-page-complete"]);
1758 | },
1759 | settings: function(context,command) {
1760 | // Type objects
1761 | var layoutTypes = ["Dense layout","Loose layout"],
1762 | sortTypes = ["Do not sort anything","Sort layers and artboards","Sort layers and artboards, reverse layer order"];
1763 |
1764 | // Default settings
1765 | var defaultSettings = {};
1766 | defaultSettings.rowCount = 8;
1767 | defaultSettings.fitWidth = '';
1768 | defaultSettings.rowDensity = 0;
1769 | defaultSettings.sortOrder = 0;
1770 | defaultSettings.xPad = "400";
1771 | defaultSettings.yPad = "600";
1772 | defaultSettings.autoSections = 1;
1773 | defaultSettings.autoTitles = 1;
1774 | defaultSettings.autoDescriptions = 1;
1775 | defaultSettings.autoAnnotations = 1;
1776 |
1777 | // Update default settings with cached settings
1778 | defaultSettings = getCachedSettings(context,sncr.page,defaultSettings,sncr.pluginDomain);
1779 |
1780 | // Check for old artboardsPerRowDefault value
1781 | var artboardsPerRowDefault = sncr.command.valueForKey_onLayer_forPluginIdentifier("artboardsPerRowDefault",sncr.page,sncr.pluginDomain);
1782 |
1783 | // If artboardsPerRowDefault value exists...
1784 | if (artboardsPerRowDefault) {
1785 | // Old artboardsPerRow object
1786 | var artboardsPerRow = ["4","6","8","10","12","14","100"];
1787 |
1788 | // Overwrite default value with old artboardsPerRow value
1789 | defaultSettings.rowCount = artboardsPerRow[artboardsPerRowDefault];
1790 | }
1791 |
1792 | // If a command is not passed, operate in config mode...
1793 | if (!command) {
1794 | var alertWindow = COSAlertWindow.new();
1795 | alertWindow.setMessageText(sncr.strings["layout-settings-title"]);
1796 |
1797 | alertWindow.addTextLabelWithValue(sncr.strings["layout-settings-artboard-count"]);
1798 |
1799 | var rowCount = createField(defaultSettings.rowCount,NSMakeRect(0,0,60,22));
1800 | alertWindow.addAccessoryView(rowCount);
1801 |
1802 | alertWindow.addTextLabelWithValue('Fit pixel width:');
1803 |
1804 | var fitWidth = createField(defaultSettings.fitWidth,NSMakeRect(0,0,60,22));
1805 | alertWindow.addAccessoryView(fitWidth);
1806 |
1807 | var fitWidthDelegate = new MochaJSDelegate({
1808 | "controlTextDidChange:" : (function() {
1809 | if (fitWidth.stringValue() != '') {
1810 | rowCount.setEnabled(0);
1811 | } else {
1812 | rowCount.setEnabled(1);
1813 | }
1814 | })
1815 | });
1816 |
1817 | fitWidth.setDelegate(fitWidthDelegate.getClassInstance());
1818 |
1819 | alertWindow.addTextLabelWithValue(sncr.strings["layout-settings-layout-type"]);
1820 |
1821 | var rowDensity = createRadioButtons(layoutTypes,defaultSettings.rowDensity);
1822 | alertWindow.addAccessoryView(rowDensity);
1823 |
1824 | alertWindow.addTextLabelWithValue(sncr.strings["layout-settings-sort-type"]);
1825 |
1826 | var sortOrder = createRadioButtons(sortTypes,defaultSettings.sortOrder);
1827 | alertWindow.addAccessoryView(sortOrder);
1828 |
1829 | alertWindow.addTextLabelWithValue(sncr.strings["layout-settings-spacing-horizontal"]);
1830 |
1831 | var xPad = createField(defaultSettings.xPad,NSMakeRect(0,0,60,22));
1832 | alertWindow.addAccessoryView(xPad);
1833 |
1834 | alertWindow.addTextLabelWithValue(sncr.strings["layout-settings-spacing-vertical"]);
1835 |
1836 | var yPad = createField(defaultSettings.yPad,NSMakeRect(0,0,60,22));
1837 | alertWindow.addAccessoryView(yPad);
1838 |
1839 | alertWindow.addTextLabelWithValue("");
1840 |
1841 | var autoSections = createCheckbox({
1842 | name : "Automatically adjust section titles",
1843 | value: 1
1844 | },defaultSettings.autoSections,NSMakeRect(0,0,300,18));
1845 |
1846 | alertWindow.addAccessoryView(autoSections);
1847 |
1848 | var autoTitles = createCheckbox({
1849 | name : "Automatically adjust artboard titles",
1850 | value: 1
1851 | },defaultSettings.autoTitles,NSMakeRect(0,0,300,18));
1852 |
1853 | alertWindow.addAccessoryView(autoTitles);
1854 |
1855 | var autoDescriptions = createCheckbox({
1856 | name : "Automatically adjust artboard descriptions",
1857 | value: 1
1858 | },defaultSettings.autoDescriptions,NSMakeRect(0,0,300,18));
1859 |
1860 | alertWindow.addAccessoryView(autoDescriptions);
1861 |
1862 | var autoAnnotations = createCheckbox({
1863 | name : "Automatically adjust layer annotations",
1864 | value: 1
1865 | },defaultSettings.autoAnnotations,NSMakeRect(0,0,300,18));
1866 |
1867 | alertWindow.addAccessoryView(autoAnnotations);
1868 |
1869 | var buttonOK = alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]);
1870 | var buttonCancel = alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]);
1871 |
1872 | // Set key order and first responder
1873 | setKeyOrder(alertWindow,[
1874 | rowCount,
1875 | fitWidth,
1876 | rowDensity,
1877 | sortOrder,
1878 | xPad,
1879 | yPad,
1880 | autoSections,
1881 | autoTitles,
1882 | autoDescriptions,
1883 | autoAnnotations,
1884 | buttonOK
1885 | ]);
1886 |
1887 | var responseCode = alertWindow.runModal();
1888 |
1889 | if (responseCode == 1000) {
1890 | try {
1891 | if (artboardsPerRowDefault) sncr.command.setValue_forKey_onLayer(nil,"artboardsPerRowDefault",sncr.page);
1892 | sncr.command.setValue_forKey_onLayer(rowCount.stringValue(),"rowCount",sncr.page);
1893 | sncr.command.setValue_forKey_onLayer(fitWidth.stringValue(),"fitWidth",sncr.page);
1894 | sncr.command.setValue_forKey_onLayer(rowDensity.selectedCell().tag(),"rowDensity",sncr.page);
1895 | sncr.command.setValue_forKey_onLayer(sortOrder.selectedCell().tag(),"sortOrder",sncr.page);
1896 | sncr.command.setValue_forKey_onLayer(xPad.stringValue(),"xPad",sncr.page);
1897 | sncr.command.setValue_forKey_onLayer(yPad.stringValue(),"yPad",sncr.page);
1898 | sncr.command.setValue_forKey_onLayer(autoSections.state(),"autoSections",sncr.page);
1899 | sncr.command.setValue_forKey_onLayer(autoTitles.state(),"autoTitles",sncr.page);
1900 | sncr.command.setValue_forKey_onLayer(autoDescriptions.state(),"autoDescriptions",sncr.page);
1901 | sncr.command.setValue_forKey_onLayer(autoAnnotations.state(),"autoAnnotations",sncr.page);
1902 | }
1903 | catch(err) {
1904 | log(sncr.strings["general-save-failed"]);
1905 | }
1906 |
1907 | sncr.layout.update(context);
1908 | } else return false;
1909 | }
1910 | // Otherwise operate in run mode...
1911 | else {
1912 | // Return settings
1913 | return {
1914 | rowCount : defaultSettings.rowCount,
1915 | fitWidth : defaultSettings.fitWidth,
1916 | rowDensity : defaultSettings.rowDensity,
1917 | sortOrder : defaultSettings.sortOrder,
1918 | xPad : defaultSettings.xPad,
1919 | yPad : defaultSettings.yPad,
1920 | autoSections : defaultSettings.autoSections,
1921 | autoTitles : defaultSettings.autoTitles,
1922 | autoDescriptions : defaultSettings.autoDescriptions,
1923 | autoAnnotations : defaultSettings.autoAnnotations
1924 | }
1925 | }
1926 | },
1927 | sanitizePages: function(context) {
1928 | var loop = sncr.pages.objectEnumerator(),
1929 | page,
1930 | pageName;
1931 |
1932 | while (page = loop.nextObject()) {
1933 | if (!sncr.command.valueForKey_onLayer(sncr.layout.config.featureKey,page)) {
1934 | sncr.command.setValue_forKey_onLayer(false,sncr.layout.config.featureKey,page);
1935 | } else {
1936 | pageName = (sncr.command.valueForKey_onLayer(sncr.layout.config.featureKey,page) == true) ? sncr.layout.config.pageNamePrefix + page.name().replace(sncr.layout.config.pageNamePrefix,"") : page.name().replace(sncr.layout.config.pageNamePrefix,"");
1937 |
1938 | page.setName(pageName);
1939 | }
1940 | }
1941 | }
1942 | }
1943 |
1944 | sncr.sections = {
1945 | linkSelected: function(context,command) {
1946 | // Validate the selections to link
1947 | var selections = sncr.sections.validateSelected(context);
1948 |
1949 | // If selections are valid...
1950 | if (selections) {
1951 | // Set stored value for linked artboard
1952 | sncr.common.linkObject(selections.title,selections.artboard.objectID(),"section");
1953 |
1954 | // Get layer name
1955 | var layerName = sncr.sections.getName(selections.title);
1956 |
1957 | // Update the layer name
1958 | selections.title.setName(layerName);
1959 |
1960 | // Create a log event
1961 | log(layerName + sncr.strings["section-link-complete"] + selections.artboard.name());
1962 |
1963 | // Update all section titles on the page
1964 | sncr.sections.updateAllOnPage(context,"link");
1965 |
1966 | // Display feedback
1967 | sketch.UI.message(layerName + sncr.strings["section-link-complete"] + selections.artboard.name());
1968 | }
1969 | },
1970 | unlinkSelected: function(context) {
1971 | // If there are selections...
1972 | if (sncr.selection.count() > 0) {
1973 | // Iterative variables
1974 | var titleName,
1975 | artboardName,
1976 | count = 0;
1977 |
1978 | // Iterate through selections...
1979 | for (var i = 0; i < sncr.selection.count(); i++) {
1980 | // Get stored value for linked artboard
1981 | var linkedArtboard = sncr.command.valueForKey_onLayer(sncr.sections.config.titleLinkKey,sncr.selection[i]);
1982 |
1983 | // If selection is linked to an artboard...
1984 | if (linkedArtboard) {
1985 | // Set linked artboard value to nil
1986 | sncr.command.setValue_forKey_onLayer(nil,sncr.sections.config.titleLinkKey,sncr.selection[i]);
1987 |
1988 | // Set the title name
1989 | titleName = sncr.selection[i].name().replace(sncr.sections.config.titleLinkPrefix,""));
1990 |
1991 | // Update the title name
1992 | sncr.selection[i].setName(titleName);
1993 |
1994 | // Unlock the section title
1995 | sncr.selection[i].setIsLocked(0);
1996 |
1997 | // For logging purposes, get linked artboard object
1998 | var artboard = findLayerByID(sncr.selection[i].parentGroup(),linkedArtboard);
1999 |
2000 | // If artboard exists, use artboard name for name, otherwise use artboard ID
2001 | artboardName = (artboard) ? artboard.name() : linkedArtboard;
2002 |
2003 | // Create a log event
2004 | log(titleName + sncr.strings["section-unlink-complete"] + artboardName);
2005 |
2006 | // Iterate the counter
2007 | count++;
2008 | }
2009 |
2010 | // Deselect current selection
2011 | sncr.selection[i].select_byExtendingSelection(0,1);
2012 | }
2013 |
2014 | // If there is only one selection...
2015 | if (sncr.selection.count() == 1) {
2016 | // Display feedback
2017 | sketch.UI.message(titleName + sncr.strings["section-unlink-complete"] + artboardName);
2018 | }
2019 | // If there is more than one selection...
2020 | else {
2021 | // Display feedback
2022 | sketch.UI.message(count + sncr.strings["section-unlinks-complete"]);
2023 | }
2024 | }
2025 | // If there are no selections...
2026 | else {
2027 | // Display feedback
2028 | sketch.UI.alert(sncr.strings["section-unlink-plugin"],sncr.strings["section-unlink-problem"]);
2029 | }
2030 | },
2031 | updateAllOnPage: function(context,command) {
2032 | // If function was invoked by action, set command
2033 | if (!command && context.actionContext) command = "action";
2034 |
2035 | logFunctionStart("Section Titles: Update",command);
2036 |
2037 | var titleSettings = sncr.sections.settings(context,"update");
2038 |
2039 | // Set remove counter
2040 | var removeCount = 0;
2041 |
2042 | // Get the section titles and construct a loop
2043 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.sections.config.titleLinkKey + " != nil",sncr.pluginDomain),
2044 | layers = sncr.page.children().filteredArrayUsingPredicate(predicate),
2045 | loop = layers.objectEnumerator(),
2046 | layer;
2047 |
2048 | // Iterate through section titles...
2049 | while (layer = loop.nextObject()) {
2050 | // Get stored value for linked artboard
2051 | var linkedArtboard = sncr.command.valueForKey_onLayer(sncr.sections.config.titleLinkKey,layer);
2052 |
2053 | // Get linked artboard object, if it resides on the artboard description page
2054 | var predicate = NSPredicate.predicateWithFormat("objectID == %@",linkedArtboard,sncr.pluginDomain),
2055 | artboard = sncr.page.artboards().filteredArrayUsingPredicate(predicate).firstObject();
2056 |
2057 | // If artboard object exists...
2058 | if (artboard) {
2059 | // Set screen title x/y in relation to artboard, with offsets
2060 | layer.frame().setX(artboard.frame().x() + titleSettings.titleXOffset);
2061 | layer.frame().setY(artboard.frame().y() + titleSettings.titleYOffset - layer.frame().height());
2062 |
2063 | if (titleSettings.titleWidth != "") {
2064 | layer.frame().setWidth(titleSettings.titleWidth);
2065 | }
2066 |
2067 | // Get layer name
2068 | var layerName = sncr.sections.getName(layer);
2069 |
2070 | // Update the layer name
2071 | layer.setName(layerName);
2072 |
2073 | // Lock the section title
2074 | layer.setIsLocked(1);
2075 | }
2076 | // If artboard object does not exist...
2077 | else {
2078 | // Remove stored value for linked artboard
2079 | sncr.command.setValue_forKey_onLayer(nil,sncr.sections.config.titleLinkKey,layer);
2080 |
2081 | // Set layer name
2082 | var layerName = layer.name().replace(sncr.sections.config.titleLinkPrefix,"");
2083 |
2084 | // Update the layer name
2085 | layer.setName(layerName);
2086 |
2087 | // Unlock the section title
2088 | layer.setIsLocked(0);
2089 |
2090 | // Create a log event
2091 | log(layerName + sncr.strings["section-unlink-complete"] + linkedArtboard);
2092 |
2093 | // Increment remove counter
2094 | removeCount++;
2095 | }
2096 | }
2097 |
2098 | // Switch message and handling per method the function was invoked
2099 | switch (command) {
2100 | case "action":
2101 | break;
2102 | case "link":
2103 | break;
2104 | case "settings":
2105 | // Display feedback
2106 | sketch.UI.message(sncr.strings["section-titles-updated"]);
2107 |
2108 | break;
2109 | default:
2110 | // If any artboard links were removed
2111 | if (removeCount > 0) {
2112 | // Display feedback
2113 | sketch.UI.message(sncr.strings["section-titles-updated"] + ", " + removeCount + sncr.strings["section-titles-updated-unlinked"]);
2114 | } else {
2115 | // Display feedback
2116 | sketch.UI.message(sncr.strings["section-titles-updated"]);
2117 | }
2118 |
2119 | break;
2120 | }
2121 | },
2122 | settings: function(context,command) {
2123 | var defaultSettings = {};
2124 | defaultSettings.sectionTitleWidth = "";
2125 | defaultSettings.sectionTitleXOffset = 0;
2126 | defaultSettings.sectionTitleYOffset = -108;
2127 |
2128 | defaultSettings = getCachedSettings(context,sncr.data,defaultSettings,sncr.pluginDomain);
2129 |
2130 | if (!command) {
2131 | var alertWindow = COSAlertWindow.new();
2132 | alertWindow.setMessageText(sncr.strings["section-settings-plugin"]);
2133 |
2134 | alertWindow.addTextLabelWithValue(sncr.strings["section-settings-width"]);
2135 |
2136 | var titleWidth = createField(defaultSettings.sectionTitleWidth,NSMakeRect(0,0,60,24));
2137 | alertWindow.addAccessoryView(titleWidth);
2138 |
2139 | alertWindow.addTextLabelWithValue(sncr.strings["section-settings-offsetX"]);
2140 |
2141 | var titleXOffset = createField(defaultSettings.sectionTitleXOffset,NSMakeRect(0,0,60,24));
2142 | alertWindow.addAccessoryView(titleXOffset);
2143 |
2144 | alertWindow.addTextLabelWithValue(sncr.strings["section-settings-offsetY"]);
2145 |
2146 | var titleYOffset = createField(defaultSettings.sectionTitleYOffset,NSMakeRect(0,0,60,24));
2147 | alertWindow.addAccessoryView(titleYOffset);
2148 |
2149 | var buttonOK = alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]);
2150 | var buttonCancel = alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]);
2151 |
2152 | setKeyOrder(alertWindow,[
2153 | titleWidth,
2154 | titleXOffset,
2155 | titleYOffset,
2156 | buttonOK
2157 | ]);
2158 |
2159 | var responseCode = alertWindow.runModal();
2160 |
2161 | if (responseCode == 1000) {
2162 | try {
2163 | sncr.command.setValue_forKey_onLayer(titleWidth.stringValue(),"sectionTitleWidth",sncr.data);
2164 | sncr.command.setValue_forKey_onLayer(Number(titleXOffset.stringValue()),"sectionTitleXOffset",sncr.data);
2165 | sncr.command.setValue_forKey_onLayer(Number(titleYOffset.stringValue()),"sectionTitleYOffset",sncr.data);
2166 | }
2167 | catch(err) {
2168 | log(sncr.strings["general-save-failed"]);
2169 | }
2170 |
2171 | sncr.sections.updateAllOnPage(context,"settings");
2172 | } else return false;
2173 | }
2174 | // Otherwise operate in run mode...
2175 | else {
2176 | // Return updated settings
2177 | return {
2178 | titleWidth : defaultSettings.sectionTitleWidth,
2179 | titleXOffset : defaultSettings.sectionTitleXOffset,
2180 | titleYOffset : defaultSettings.sectionTitleYOffset
2181 | }
2182 | }
2183 | },
2184 | validateSelected: function(context) {
2185 | // Get latest selections, as they may have been changed by Insert
2186 | let selections = sketch.getSelectedDocument().selectedLayers
2187 |
2188 | // If two objects are not selected...
2189 | if (selections.length != 2) {
2190 | // Display feedback
2191 | sketch.UI.alert(sncr.strings["section-link-plugin"],sncr.strings["section-link-problem"])
2192 |
2193 | return false
2194 | }
2195 |
2196 | // Selection variables
2197 | let firstObject = selections.layers[0].sketchObject
2198 | let lastObject = selections.layers[1].sketchObject
2199 |
2200 | // If the first item is not an artboard and the second item is an artboard...
2201 | if ((firstObject.class() != "MSArtboardGroup" || firstObject.class() != "MSSymbolMaster") && (lastObject.class() == "MSArtboardGroup" || lastObject.class() == "MSSymbolMaster")) {
2202 | return {
2203 | title : firstObject,
2204 | artboard : lastObject
2205 | }
2206 | }
2207 | // If the first item is an artboard and the second item is not an artboard
2208 | else if ((firstObject.class() == "MSArtboardGroup" || firstObject.class() == "MSSymbolMaster") && (lastObject.class() != "MSArtboardGroup" || lastObject.class() != "MSSymbolMaster")) {
2209 | return {
2210 | title : lastObject,
2211 | artboard : firstObject
2212 | }
2213 | }
2214 | // If the selections are two artboards...
2215 | else {
2216 | // Display feedback
2217 | sketch.UI.alert(sncr.strings["section-link-plugin"],sncr.strings["section-link-problem"])
2218 |
2219 | return false
2220 | }
2221 | },
2222 | getName: function(object) {
2223 | var name;
2224 |
2225 | if (object.class() == "MSSymbolInstance" && object.overrides().allValues().firstObject()) {
2226 | name = sncr.sections.config.titleLinkPrefix + object.overrides().allValues().firstObject();
2227 | } else if (object.class() == "MSTextLayer") {
2228 | name = sncr.sections.config.titleLinkPrefix + object.stringValue();
2229 | } else {
2230 | name = sncr.sections.config.titleLinkPrefix + object.name().replace(sncr.sections.config.titleLinkPrefix,"");
2231 | }
2232 |
2233 | return name;
2234 | },
2235 |
2236 | }
2237 |
2238 | sncr.titles = {
2239 | create: function(context,command) {
2240 | // If function was invoked by action, set command
2241 | if (!command && context.actionContext) command = "action";
2242 |
2243 | logFunctionStart("Artboard Titles: Create",command);
2244 |
2245 | var titleSettings = sncr.titles.settings(context,"create");
2246 |
2247 | // Set parent group
2248 | var parentGroup = getParentGroup(sncr.page,sncr.parentGroupName);
2249 |
2250 | // Find and remove screen titles group if it exists on the page (the old location)
2251 | sncr.page.removeLayer(findLayerByName(sncr.page,sncr.titlesGroupName));
2252 |
2253 | // Find and remove screen titles group if it exists in the parent group (the new location)
2254 | parentGroup.removeLayer(findLayerByName(parentGroup,sncr.titlesGroupName));
2255 |
2256 | // Get a filtered list of artboards
2257 | var predicate = NSPredicate.predicateWithFormat("userInfo == nil || function(userInfo,'valueForKeyPath:',%@)." + sncr.titles.config.featureKey + " != " + false,sncr.pluginDomain),
2258 | artboards = sncr.page.artboards().filteredArrayUsingPredicate(predicate),
2259 | loop = artboards.objectEnumerator(),
2260 | artboard;
2261 |
2262 | // If artboards exist on the page...
2263 | if (artboards.length) {
2264 | // Screen title style
2265 | var screenTitleStyleName = "Wireframe/Screen Title";
2266 | var screenTitleStyleData = {
2267 | fontFace : "Neue Haas Grotesk Text Std 75 Bold",
2268 | fontSize : 18,
2269 | lineHeight : 48,
2270 | textAlignment : 0
2271 | }
2272 |
2273 | // Screen title settings
2274 | var screenTitleOffset = parseInt(titleSettings.titleOffset);
2275 |
2276 | // Remove screen title style (the old style)
2277 | deleteTextStyle('Layout/Screen Title');
2278 |
2279 | // Get screen title style (will add style if it doesn't exist) (the new style)
2280 | var screenTitleStyle = getTextStyle(screenTitleStyleName,screenTitleStyleData);
2281 |
2282 | // Create new screen title group
2283 | var titleGroup = MSLayerGroup.new();
2284 | titleGroup.setName(sncr.titlesGroupName);
2285 | titleGroup.frame().setX(0 - parentGroup.frame().x());
2286 | titleGroup.frame().setY(0 - parentGroup.frame().y());
2287 | titleGroup.setHasClickThrough(true);
2288 |
2289 | // Iterate through the artboards...
2290 | while (artboard = loop.nextObject()) {
2291 | // Create a screen title
2292 | var screenTitle = MSTextLayer.new();
2293 | screenTitle.setStringValue(artboard.name());
2294 | screenTitle.setName(artboard.name());
2295 |
2296 | // Apply style to screen title
2297 | if (screenTitle.newInstance) {
2298 | screenTitle.setStyle(screenTitleStyle.newInstance());
2299 | } else {
2300 | screenTitle.setSharedStyle(screenTitleStyle);
2301 | }
2302 |
2303 | // Set screen title x/y position
2304 | screenTitle.frame().setX(artboard.frame().x());
2305 | screenTitle.frame().setY(artboard.frame().y() + artboard.frame().height() + screenTitleOffset);
2306 |
2307 | // If user wants screen title below artboards...
2308 | if (titleSettings.titleType == 0) {
2309 | // Adjust screen title y position
2310 | screenTitle.frame().setY(artboard.frame().y() - (screenTitleStyleData.lineHeight + screenTitleOffset));
2311 | }
2312 |
2313 | // Add screen title to title group
2314 | titleGroup.addLayers([screenTitle]);
2315 | }
2316 |
2317 | // Add title group to parent group
2318 | parentGroup.addLayers([titleGroup]);
2319 |
2320 | // Resize title and parents groups to account for children
2321 | if (sketch.version.sketch > 52) {
2322 | titleGroup.fixGeometryWithOptions(0);
2323 | parentGroup.fixGeometryWithOptions(0);
2324 | } else {
2325 | titleGroup.resizeToFitChildrenWithOption(0);
2326 | parentGroup.resizeToFitChildrenWithOption(0);
2327 | }
2328 |
2329 | // Collapse the parent group
2330 | parentGroup.setLayerListExpandedType(0);
2331 |
2332 | // Move parent group to the top of the layer list
2333 | parentGroup.moveToLayer_beforeLayer(sncr.page,nil);
2334 |
2335 | // Deselect parent group (moveToLayer_beforeLayer selects it)
2336 | parentGroup.select_byExtendingSelection(0,1);
2337 |
2338 | // Switch message and handling per method the function was invoked
2339 | switch (command) {
2340 | case "include":
2341 | break;
2342 | case "preclude":
2343 | break;
2344 | case "action":
2345 | break;
2346 | case "settings":
2347 | break;
2348 | case "layout":
2349 | break;
2350 | default:
2351 | // Lock the parent group
2352 | parentGroup.setIsLocked(1);
2353 |
2354 | // Display feedback
2355 | sketch.UI.message(sncr.strings["title-create-complete"]);
2356 |
2357 | break;
2358 | }
2359 | }
2360 | // If no artboards exist on the page...
2361 | else {
2362 | // Switch message and handling per method the function was invoked
2363 | switch (command) {
2364 | case "include":
2365 | break;
2366 | case "preclude":
2367 | break;
2368 | case "action":
2369 | break;
2370 | case "settings":
2371 | break;
2372 | case "layout":
2373 | break;
2374 | default:
2375 | // Display feedback
2376 | sketch.UI.alert(sncr.strings["title-create-plugin"],sncr.strings["title-create-problem"]);
2377 |
2378 | break;
2379 | }
2380 | }
2381 | },
2382 | include: function(context) {
2383 | var count = 0;
2384 |
2385 | if (sncr.selection.count()) {
2386 | for (var i = 0; i < sncr.selection.count(); i++) {
2387 | if (sncr.selection[i] instanceof MSArtboardGroup) {
2388 | sncr.command.setValue_forKey_onLayer(true,sncr.titles.config.featureKey,sncr.selection[i]);
2389 |
2390 | count++;
2391 |
2392 | log(sncr.selection[i].name() + sncr.strings["title-include-complete"]);
2393 | }
2394 | }
2395 |
2396 | sncr.titles.create(context,"include");
2397 |
2398 | if (sncr.selection.count() == 1) {
2399 | sketch.UI.message(sncr.selection[0].name() + sncr.strings["title-include-complete"]);
2400 | } else {
2401 | sketch.UI.message(count + sncr.strings["title-includes-complete"]);
2402 | }
2403 | } else {
2404 | sketch.UI.alert(sncr.strings["title-include-plugin"],sncr.strings["title-include-problem"]);
2405 | }
2406 | },
2407 | preclude: function(context) {
2408 | var count = 0;
2409 |
2410 | if (sncr.selection.count()) {
2411 | for (var i = 0; i < sncr.selection.count(); i++) {
2412 | if (sncr.selection[i] instanceof MSArtboardGroup) {
2413 | sncr.command.setValue_forKey_onLayer(false,sncr.titles.config.featureKey,sncr.selection[i]);
2414 |
2415 | count++;
2416 |
2417 | log(sncr.selection[i].name() + sncr.strings["title-preclude-complete"]);
2418 | }
2419 | }
2420 |
2421 | sncr.titles.create(context,"preclude");
2422 |
2423 | if (sncr.selection.count() == 1) {
2424 | sketch.UI.message(sncr.selection[0].name() + sncr.strings["title-preclude-complete"]);
2425 | } else {
2426 | sketch.UI.message(count + sncr.strings["title-precludes-complete"]);
2427 | }
2428 | } else {
2429 | sketch.UI.alert(sncr.strings["title-preclude-plugin"],sncr.strings["title-preclude-problem"]);
2430 | }
2431 | },
2432 | settings: function(context,command) {
2433 | // Setting variables
2434 | var defaultSettings = {};
2435 | defaultSettings.artboardTitleType = 0;
2436 | defaultSettings.artboardTitleOffset = 0;
2437 | defaultSettings.artboardTitleAuto = 0;
2438 |
2439 | // Update default settings with cached settings
2440 | defaultSettings = getCachedSettings(context,sncr.data,defaultSettings,sncr.pluginDomain);
2441 |
2442 | // If a command is not passed, operate in config mode...
2443 | if (!command) {
2444 | var alertWindow = COSAlertWindow.new();
2445 | alertWindow.setMessageText('Create Artboard Titles');
2446 |
2447 | var titleType = createRadioButtons(["Above artboards","Below artboards"],defaultSettings.artboardTitleType);
2448 | alertWindow.addAccessoryView(titleType);
2449 |
2450 | alertWindow.addTextLabelWithValue('Vertical offset:');
2451 |
2452 | var titleOffset = createField(defaultSettings.artboardTitleOffset,NSMakeRect(0,0,60,24));
2453 | alertWindow.addAccessoryView(titleOffset);
2454 |
2455 | // Buttons
2456 | var buttonOK = alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]);
2457 | var buttonCancel = alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]);
2458 |
2459 | // Set key order and first responder
2460 | setKeyOrder(alertWindow,[
2461 | titleType,
2462 | titleOffset,
2463 | buttonOK
2464 | ]);
2465 |
2466 | var responseCode = alertWindow.runModal();
2467 |
2468 | if (responseCode == 1000) {
2469 | try {
2470 | // Purge old settings in old location
2471 | sncr.command.setValue_forKey_onLayer(nil,"titleType",sncr.page);
2472 | sncr.command.setValue_forKey_onLayer(nil,"titleOffset",sncr.page);
2473 |
2474 | // Save new settings in new location
2475 | sncr.command.setValue_forKey_onLayer(titleType.selectedCell().tag(),"artboardTitleType",sncr.data);
2476 | sncr.command.setValue_forKey_onLayer(Number(titleOffset.stringValue()),"artboardTitleOffset",sncr.data);
2477 | }
2478 | catch(err) {
2479 | log(sncr.strings["general-save-failed"]);
2480 | }
2481 |
2482 | sncr.titles.create(context,"settings");
2483 | } else return false;
2484 | }
2485 | // Otherwise operate in run mode...
2486 | else {
2487 | // Return updated settings
2488 | return {
2489 | titleType : defaultSettings.artboardTitleType,
2490 | titleOffset : defaultSettings.artboardTitleOffset
2491 | }
2492 | }
2493 | }
2494 | }
2495 |
2496 | sncr.wireframes = {
2497 | addNew: function(context) {
2498 | var predicate = NSPredicate.predicateWithFormat("userInfo == nil || function(userInfo,'valueForKeyPath:',%@)." + sncr.layout.config.featureKey + " != " + false,sncr.pluginDomain),
2499 | artboards = sncr.page.artboards().filteredArrayUsingPredicate(predicate),
2500 | selectionSize = getSelectionSize(artboards);
2501 |
2502 | var margin = 400,
2503 | sliceX = 400,
2504 | sliceY = 1000,
2505 | sliceWidth = selectionSize.width + sliceX + margin,
2506 | sliceHeight = selectionSize.height + sliceY + margin,
2507 | minWidth = 6680,
2508 | minHeight = 4520;
2509 |
2510 | sliceWidth = (sliceWidth < minWidth) ? minWidth : sliceWidth;
2511 | sliceHeight = (sliceHeight < minHeight) ? minHeight : sliceHeight;
2512 |
2513 | var sliceLayer = [MSSliceLayer new];
2514 | sliceLayer.setName(sncr.page.name().replace(sncr.layout.config.pageNamePrefix,""));
2515 | sliceLayer.setBackgroundColor(MSColor.colorWithRed_green_blue_alpha(239/255,239/255,239/255,1.0));
2516 | sliceLayer.setIsLocked(1);
2517 | sliceLayer.hasBackgroundColor = true;
2518 | sliceLayer.frame().setX(-sliceX);
2519 | sliceLayer.frame().setY(-sliceY);
2520 | sliceLayer.frame().setWidth(sliceWidth);
2521 | sliceLayer.frame().setHeight(sliceHeight);
2522 |
2523 | sncr.page.addLayers([sliceLayer]);
2524 |
2525 | sliceLayer.select_byExtendingSelection(1,0);
2526 | actionWithType(context,"MSMoveToBackAction").doPerformAction(nil);
2527 |
2528 | var format = sliceLayer.exportOptions().addExportFormat();
2529 | format.setScale(".5");
2530 | format.setFileFormat("pdf");
2531 |
2532 | sncr.wireframes.include(context);
2533 |
2534 | sketch.UI.message(sncr.strings["wireframe-add-complete"]);
2535 |
2536 | function getSelectionSize(selection) {
2537 | var minX,minY,maxX,maxY;
2538 | minX=minY=Number.MAX_VALUE;
2539 | maxX=maxY=-0xFFFFFFFF;
2540 |
2541 | for (var i = 0; i < selection.count(); i++) {
2542 | var frame = selection.objectAtIndex(i).frame();
2543 |
2544 | minX = Math.min(minX,frame.minX());
2545 | minY = Math.min(minY,frame.minY());
2546 | maxX = Math.max(maxX,frame.maxX());
2547 | maxY = Math.max(maxY,frame.maxY());
2548 | }
2549 |
2550 | return {
2551 | width: maxX-minX,
2552 | height: maxY-minY,
2553 | minX: minX,
2554 | minY: minY
2555 | };
2556 | }
2557 | },
2558 | include: function(context) {
2559 | var selection = sncr.page.selectedLayers().layers();
2560 |
2561 | if (selection.count() == 1 && selection[0] instanceof MSSliceLayer) {
2562 | sncr.command.setValue_forKey_onLayer(true,sncr.wireframes.config.featureKey,selection[0]);
2563 |
2564 | sketch.UI.message(selection[0].name() + sncr.strings["wireframe-include-complete"]);
2565 | } else {
2566 | sketch.UI.alert(sncr.strings["wireframe-include-plugin"],sncr.strings["wireframe-include-problem"]);
2567 | }
2568 | },
2569 | preclude: function(context) {
2570 | if (sncr.selection.count() == 1 && sncr.selection[0] instanceof MSSliceLayer) {
2571 | sncr.command.setValue_forKey_onLayer(false,sncr.wireframes.config.featureKey,sncr.selection[0]);
2572 |
2573 | sketch.UI.message(sncr.selection[0].name() + sncr.strings["wireframe-preclude-complete"]);
2574 | } else {
2575 | sketch.UI.alert(sncr.strings["wireframe-preclude-plugin"],sncr.strings["wireframe-preclude-problem"]);
2576 | }
2577 | },
2578 | export: function(context) {
2579 | var loop = sncr.pages.objectEnumerator(),
2580 | page,
2581 | wireframes = [],
2582 | count = 1;
2583 |
2584 | while (page = loop.nextObject()) {
2585 | var predicate = NSPredicate.predicateWithFormat("className == 'MSSliceLayer' && userInfo != nil && function(userInfo,'valueForKeyPath:',%@)." + sncr.wireframes.config.featureKey + " == " + true,sncr.pluginDomain),
2586 | slices = page.children().filteredArrayUsingPredicate(predicate),
2587 | loop2 = slices.objectEnumerator(),
2588 | slice;
2589 |
2590 | while (slice = loop2.nextObject()) {
2591 | var filePrefix = "W",
2592 | filename = filePrefix + count + " " + slice.name(),
2593 | filepath = [@"~/Downloads" stringByExpandingTildeInPath] + "/" + filename + ".pdf";
2594 |
2595 | wireframes.push({
2596 | source : slice,
2597 | name : filename,
2598 | path : filepath
2599 | });
2600 |
2601 | count++;
2602 | }
2603 | }
2604 |
2605 | if (wireframes.length) {
2606 | var exportList = sncr.wireframes.confirm(wireframes);
2607 |
2608 | if (exportList.length) {
2609 | var doc = sncr.document;
2610 |
2611 | for (var i = 0; i < exportList.length; i++) {
2612 | [doc saveArtboardOrSlice:exportList[i]['source'] toFile:exportList[i]['path']];
2613 | }
2614 |
2615 | sketch.UI.message(exportList.length + sncr.strings["wireframe-export-complete"]);
2616 | }
2617 | } else {
2618 | sketch.UI.alert(sncr.strings["wireframe-export-plugin"],sncr.strings["wireframe-export-problem"]);
2619 | }
2620 | },
2621 | confirm: function(wireframes) {
2622 | var alertWindow = COSAlertWindow.new();
2623 |
2624 | alertWindow.setMessageText(sncr.strings["wireframe-export-plugin"]);
2625 |
2626 | alertWindow.addTextLabelWithValue(sncr.strings["wireframe-export-intro"]);
2627 |
2628 | var wireframeListHeader = NSView.alloc().initWithFrame(NSMakeRect(0,0,300,18)),
2629 | wireframeListCheckbox = createCheckbox({name:"",value:1},1,NSMakeRect(0,0,18,18)),
2630 | wireframeListLabel = createBoldLabel("Wireframes (" + wireframes.length + ")",12,NSMakeRect(22,0,300-22,18));
2631 |
2632 | wireframeListCheckbox.setAction("callAction:");
2633 | wireframeListCheckbox.setCOSJSTargetFunction(function(sender) {
2634 | for (var i = 0; i < wireframeListCheckboxes.length; i++) {
2635 | wireframeListCheckboxes[i].state = sender.state();
2636 | }
2637 | });
2638 |
2639 | wireframeListHeader.addSubview(wireframeListCheckbox);
2640 | wireframeListHeader.addSubview(wireframeListLabel);
2641 |
2642 | alertWindow.addAccessoryView(wireframeListHeader);
2643 |
2644 | var wireframeListItem = 24,
2645 | wireframeListContent = NSView.alloc().initWithFrame(NSMakeRect(0,0,300,wireframes.length*wireframeListItem)),
2646 | wireframeListCheckboxes = [],
2647 | count = 0;
2648 |
2649 | wireframeListContent.setFlipped(1);
2650 | wireframeListContent.setWantsLayer(1);
2651 | wireframeListContent.layer().setBackgroundColor(CGColorCreateGenericRGB(255,255,255,1));
2652 |
2653 | for (var i = 0; i < wireframes.length; i++) {
2654 | var wireframeCheckbox = createCheckbox({name:wireframes[i]['name'],value:i},1,NSMakeRect(4,count*wireframeListItem,300,wireframeListItem));
2655 |
2656 | wireframeListCheckboxes.push(wireframeCheckbox);
2657 | wireframeListContent.addSubview(wireframeCheckbox);
2658 |
2659 | count++;
2660 | }
2661 |
2662 | alertWindow.addAccessoryView(wireframeListContent);
2663 |
2664 | alertWindow.addTextLabelWithValue(sncr.strings["wireframe-export-outro"]);
2665 |
2666 | var buttonOK = alertWindow.addButtonWithTitle(sncr.strings["general-button-ok"]);
2667 | var buttonCancel = alertWindow.addButtonWithTitle(sncr.strings["general-button-cancel"]);
2668 |
2669 | var responseCode = alertWindow.runModal();
2670 |
2671 | if (responseCode == 1000) {
2672 | var slicesToRemove = [];
2673 |
2674 | for (var i = 0; i < wireframeListCheckboxes.length; i++) {
2675 | if (wireframeListCheckboxes[i].state() == 0) {
2676 | slicesToRemove.push(wireframeListCheckboxes[i].tag());
2677 | }
2678 | }
2679 |
2680 | slicesToRemove.sort(function(a,b){ return b-a; });
2681 |
2682 | for (var i = 0; i < slicesToRemove.length; i++) {
2683 | wireframes.splice(slicesToRemove[i],1);
2684 | }
2685 |
2686 | return wireframes;
2687 | } else return false;
2688 | }
2689 | }
2690 |
2691 | function createBoldLabel(text,size,frame) {
2692 | var label = NSTextField.alloc().initWithFrame(frame);
2693 |
2694 | label.setStringValue(text);
2695 | label.setFont(NSFont.boldSystemFontOfSize(size));
2696 | label.setBezeled(0);
2697 | label.setDrawsBackground(0);
2698 | label.setEditable(0);
2699 | label.setSelectable(0);
2700 |
2701 | return label;
2702 | }
2703 |
2704 | var createCheckbox = function(item,flag,frame) {
2705 | flag = ( flag == false ) ? NSOffState : NSOnState;
2706 | var checkbox = [[NSButton alloc] initWithFrame:frame];
2707 | [checkbox setButtonType: NSSwitchButton];
2708 | [checkbox setBezelStyle: 0];
2709 | [checkbox setTitle: item.name];
2710 | [checkbox setTag: item.value];
2711 | [checkbox setState: flag];
2712 |
2713 | return checkbox;
2714 | }
2715 |
2716 | function createSelect(items,selectedItemIndex,frame) {
2717 | var comboBox = NSComboBox.alloc().initWithFrame(frame),
2718 | selectedItemIndex = (selectedItemIndex > -1) ? selectedItemIndex : 0;
2719 |
2720 | comboBox.addItemsWithObjectValues(items);
2721 | comboBox.selectItemAtIndex(selectedItemIndex);
2722 | comboBox.setNumberOfVisibleItems(16);
2723 |
2724 | return comboBox;
2725 | }
2726 |
2727 | var createField = function(value,size) {
2728 | var size = (size) ? size : NSMakeRect(0,0,100,20);
2729 | var field = [[NSTextField alloc] initWithFrame:size];
2730 | [field setStringValue:value];
2731 |
2732 | return field;
2733 | }
2734 |
2735 | var createLabel = function(text,frame) {
2736 | var label = [[NSTextField alloc] initWithFrame:frame];
2737 | [label setStringValue:text];
2738 | [label setFont:[NSFont boldSystemFontOfSize:12]];
2739 | [label setBezeled:false];
2740 | [label setDrawsBackground:false];
2741 | [label setEditable:false];
2742 | [label setSelectable:false];
2743 |
2744 | return label;
2745 | }
2746 |
2747 | function actionWithType(context,type) {
2748 | var controller = context.document.actionsController();
2749 |
2750 | if (controller.actionWithName) {
2751 | return controller.actionWithName(type);
2752 | } else if (controller.actionWithID) {
2753 | return controller.actionWithID(type);
2754 | } else {
2755 | return controller.actionForID(type);
2756 | }
2757 | }
2758 |
2759 | function createRadioButtons(options,selected) {
2760 | // Set number of rows and columns
2761 | var rows = options.length;
2762 | var columns = 1;
2763 |
2764 | // Make a prototype cell
2765 | var buttonCell = [[NSButtonCell alloc] init];
2766 | [buttonCell setButtonType:NSRadioButton]
2767 |
2768 | // Make a matrix to contain the radio cells
2769 | var buttonMatrix = [[NSMatrix alloc] initWithFrame: NSMakeRect(20,20,300,rows*25) mode:NSRadioModeMatrix prototype:buttonCell numberOfRows:rows numberOfColumns:columns];
2770 | [buttonMatrix setCellSize: NSMakeSize(300,20)];
2771 |
2772 | // Create a cell for each option
2773 | for (i = 0; i < options.length; i++) {
2774 | [[[buttonMatrix cells] objectAtIndex: i] setTitle: options[i]];
2775 | [[[buttonMatrix cells] objectAtIndex: i] setTag: i];
2776 | }
2777 |
2778 | // Select the default cell
2779 | [buttonMatrix selectCellAtRow:selected column:0]
2780 |
2781 | // Return the matrix
2782 | return buttonMatrix;
2783 | }
2784 |
2785 | function findLayerByID(scope,layerID,type) {
2786 | var scope = scope.layers();
2787 |
2788 | if (scope) {
2789 | for (var i = 0; i < scope.count(); i++) {
2790 | var layer = scope.objectAtIndex(i);
2791 |
2792 | if ((!type && layer.objectID() == layerID) || (type && layer.objectID() == layerID && layer instanceof type)) {
2793 | return layer;
2794 | }
2795 | }
2796 | }
2797 |
2798 | return false;
2799 | }
2800 |
2801 | function findLayerByName(scope,layerName,type) {
2802 | var scope = scope.layers();
2803 |
2804 | if (scope) {
2805 | for (var i = 0; i < scope.count(); i++) {
2806 | var name = scope.objectAtIndex(i).name().trim();
2807 |
2808 | if ((!type && name == layerName) || (type && name == layerName && scope.objectAtIndex(i) instanceof type)) {
2809 | return scope.objectAtIndex(i);
2810 | }
2811 | }
2812 | }
2813 |
2814 | return false;
2815 | }
2816 |
2817 | function getLayerIndex(layer) {
2818 | var layers = layer.parentGroup().layers();
2819 |
2820 | for (var i = 0; i < layers.count(); i++) {
2821 | if (layers.objectAtIndex(i) == layer) return i;
2822 | }
2823 |
2824 | return false;
2825 | }
2826 |
2827 | function getObjectByName(haystack,needle) {
2828 | for (var i = 0; i < haystack.count(); i++) {
2829 | var objectName = haystack.objectAtIndex(i).name();
2830 |
2831 | if (objectName && objectName.isEqualToString(needle)) {
2832 | return haystack.objectAtIndex(i);
2833 | }
2834 | }
2835 |
2836 | return false;
2837 | }
2838 |
2839 | function getTextStyle(styleName,styleData) {
2840 | var textStyles = MSDocument.currentDocument().documentData().layerTextStyles(),
2841 | textStyle = getObjectByName(textStyles.objects(),styleName);
2842 |
2843 | if (!textStyle) {
2844 | var textLayer = MSTextLayer.alloc().initWithFrame(nil);
2845 | textLayer.setFontSize(styleData.fontSize);
2846 | textLayer.setLineHeight(styleData.lineHeight);
2847 | textLayer.setTextAlignment(styleData.textAlignment);
2848 | textLayer.setFontPostscriptName(styleData.fontFace);
2849 |
2850 | if (textStyles.addSharedStyleWithName_firstInstance) {
2851 | textStyle = textStyles.addSharedStyleWithName_firstInstance(styleName,textLayer.style());
2852 | } else if (textStyles.initWithName_firstInstance) {
2853 | textStyle = MSSharedStyle.alloc().initWithName_firstInstance(styleName,textLayer.style());
2854 |
2855 | textStyles.addSharedObject(textStyle);
2856 | } else {
2857 | textStyle = MSSharedStyle.alloc().initWithName_style(styleName,textLayer.style());
2858 |
2859 | textStyles.addSharedObject(textStyle);
2860 | }
2861 | }
2862 |
2863 | return textStyle;
2864 | }
2865 |
2866 | function deleteTextStyle(styleName) {
2867 | var textStyles = MSDocument.currentDocument().documentData().layerTextStyles(),
2868 | textStyle = getObjectByName(textStyles.objects(),styleName);
2869 |
2870 | if (textStyle) textStyles.removeSharedStyle(textStyle);
2871 | }
2872 |
2873 | function getParentGroup(scope,name) {
2874 | var group = findLayerByName(scope,name);
2875 |
2876 | if (!group) {
2877 | var group = MSLayerGroup.new();
2878 | group.setName(name);
2879 | group.frame().setX(0);
2880 | group.frame().setY(0);
2881 | group.setIsLocked(1);
2882 |
2883 | scope.addLayers([group]);
2884 | }
2885 |
2886 | group.setHasClickThrough(true);
2887 |
2888 | return group;
2889 | }
2890 |
2891 | function getChildGroup(scope,name) {
2892 | var group = findLayerByName(scope,name);
2893 |
2894 | if (!group) {
2895 | var group = MSLayerGroup.new();
2896 | group.setName(name);
2897 | group.frame().setX(0 - scope.frame().x());
2898 | group.frame().setY(0 - scope.frame().y());
2899 |
2900 | scope.addLayers([group]);
2901 | }
2902 |
2903 | group.setHasClickThrough(true);
2904 |
2905 | return group;
2906 | }
2907 |
2908 | function setKeyOrder(alert,order) {
2909 | for (var i = 0; i < order.length; i++) {
2910 | var thisItem = order[i];
2911 | var nextItem = order[i+1];
2912 |
2913 | if (nextItem) thisItem.setNextKeyView(nextItem);
2914 | }
2915 |
2916 | alert.alert().window().setInitialFirstResponder(order[0]);
2917 | }
2918 |
2919 | function getCachedSettings(context,location,settings,domain) {
2920 | try {
2921 | for (i in settings) {
2922 | var value = sncr.command.valueForKey_onLayer_forPluginIdentifier(i,location,domain);
2923 | if (value) settings[i] = value;
2924 | }
2925 |
2926 | return settings;
2927 | } catch(err) {
2928 | log("Unable to fetch settings");
2929 | }
2930 | }
2931 |
2932 | function logFunctionStart(output,command) {
2933 | if (!command) command = "user";
2934 |
2935 | log(output + " - Initiated by " + command);
2936 | }
2937 |
2938 | function createNewRectForFlowLayer(linkedObject,flowObjectID) {
2939 | var objectRect = linkedObject.absoluteRect().rect();
2940 |
2941 | var master = linkedObject.symbolMaster();
2942 | var masterRect = master.absoluteRect().rect();
2943 |
2944 | var flowRect = master.layerWithID(flowObjectID).absoluteRect().rect();
2945 |
2946 |
2947 |
2948 |
2949 |
2950 |
2951 | // log(flowRect)
2952 | //
2953 | // var overrides = linkedObject.overrideContainer().flattenedChildren();
2954 | //
2955 | // overrides.forEach(o => {
2956 | // let value = String(o.availableOverride().internalOverrideValue());
2957 | //
2958 | // if (value.includes('flowDestination')) {
2959 | // let path = (value.includes('/')) ? value.substr(0,value.indexOf('/')) : value;
2960 | //
2961 | // if (path == flowObjectID) {
2962 | // var foo = CGPathGetPathBoundingBox(o.pathInInstance());
2963 | //
2964 | // var parentRect = linkedObject.absoluteRect().rect();
2965 | //
2966 | // let w = parentRect.origin.x + foo.origin.x;
2967 | // let x = parentRect.origin.y + foo.origin.y;
2968 | // let y = foo.size.width;
2969 | // let z = foo.size.height;
2970 | //
2971 | // return NSMakeRect(w,x,y,z);
2972 | // }
2973 | // }
2974 | // });
2975 |
2976 |
2977 |
2978 |
2979 |
2980 |
2981 | var offsetX = flowRect.origin.x - masterRect.origin.x;
2982 | var offsetY = flowRect.origin.y - masterRect.origin.y;
2983 | var offsetWidth = objectRect.size.width - masterRect.size.width;
2984 | var offsetHeight = objectRect.size.height - masterRect.size.height;
2985 |
2986 | var newX = objectRect.origin.x + offsetX + offsetWidth;
2987 | var newY = objectRect.origin.y + offsetY + offsetHeight;
2988 |
2989 | return NSMakeRect(newX,newY,flowRect.size.width,flowRect.size.height);
2990 | }
2991 |
--------------------------------------------------------------------------------