├── .gitattributes
├── Artboard Framer.sketchplugin
└── Contents
│ ├── Resources
│ ├── Artboard Framer.sketch
│ └── icon.png
│ └── Sketch
│ ├── delegate.js
│ ├── manifest.json
│ └── script.js
├── LICENSE.md
├── README.md
├── appcast.xml
└── images
├── logo.png
└── screenshot.png
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/Artboard Framer.sketchplugin/Contents/Resources/Artboard Framer.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/artboard-framer/2e9087e73dccddfbef56c1a02bcc005ca1c02fde/Artboard Framer.sketchplugin/Contents/Resources/Artboard Framer.sketch
--------------------------------------------------------------------------------
/Artboard Framer.sketchplugin/Contents/Resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/artboard-framer/2e9087e73dccddfbef56c1a02bcc005ca1c02fde/Artboard Framer.sketchplugin/Contents/Resources/icon.png
--------------------------------------------------------------------------------
/Artboard Framer.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 |
--------------------------------------------------------------------------------
/Artboard Framer.sketchplugin/Contents/Sketch/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "Artboard Framer",
3 | "description" : "Frame your artboards with a device image or wireframe.",
4 | "author" : "Jason Burns",
5 | "homepage" : "https://github.com/sonburn/artboard-framer",
6 | "version" : "0.13",
7 | "identifier" : "com.sonburn.sketchplugins.artboard-framer",
8 | "appcast" : "https://raw.githubusercontent.com/sonburn/artboard-framer/master/appcast.xml",
9 | "icon" : "icon.png",
10 | "commands" : [
11 | {
12 | "name" : "Frame All Artboards",
13 | "identifier" : "frameAll",
14 | "description" : "Frame all artboards with a device image or wireframe.",
15 | "icon" : "icon.png",
16 | "script" : "script.js",
17 | "handler" : "frameAll"
18 | },
19 | {
20 | "name" : "Frame Selected Artboards",
21 | "identifier" : "frameSelected",
22 | "description" : "Frame selected artboards with a device image or wireframe.",
23 | "icon" : "icon.png",
24 | "script" : "script.js",
25 | "handler" : "frameSelected"
26 | },
27 | {
28 | "name" : "Request Frame/Report Issue",
29 | "identifier" : "report",
30 | "description" : "Report an issue with Artboard Framer.",
31 | "icon" : "icon.png",
32 | "script" : "script.js",
33 | "handler" : "report"
34 | },
35 | {
36 | "name" : "Other Plugins",
37 | "identifier" : "plugins",
38 | "description" : "View all of Sonburn's plugins.",
39 | "icon" : "icon.png",
40 | "script" : "script.js",
41 | "handler" : "plugins"
42 | },
43 | {
44 | "name" : "Donate",
45 | "identifier" : "donate",
46 | "description" : "Donate to the development of Artboard Framer.",
47 | "icon" : "icon.png",
48 | "script" : "script.js",
49 | "handler" : "donate"
50 | }
51 | ],
52 | "menu" : {
53 | "title" : "Artboard Framer",
54 | "items" : [
55 | "frameAll",
56 | "frameSelected",
57 | "-",
58 | "report",
59 | "plugins",
60 | "donate"
61 | ]
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Artboard Framer.sketchplugin/Contents/Sketch/script.js:
--------------------------------------------------------------------------------
1 | @import "delegate.js";
2 |
3 | var sketch = require("sketch");
4 |
5 | // Plugin variables
6 | var pluginName = "Artboard Framer",
7 | pluginDescription = "Frame your artboards with a device image or wireframe.",
8 | pluginIdentifier = "com.sonburn.sketchplugins.artboard-framer",
9 | frameLibraryID = "D16D094A-4631-43DB-A362-B9D66057F333",
10 | frameLibrary,
11 | frameGroup,
12 | debugMode = false;
13 |
14 | var frameAll = function(context) {
15 | // Get artboards on current page
16 | var artboards = context.document.currentPage().artboards();
17 |
18 | // If artboards are present...
19 | if (artboards.count() > 0) {
20 | // Frame the artboards
21 | createFrames(context,artboards);
22 |
23 | if (!debugMode) googleAnalytics(context,"frameAll","run");
24 | }
25 | // If no artboards are present...
26 | else {
27 | // Display feedback
28 | displayDialog("There are no artboards on the page.",pluginName);
29 | }
30 | }
31 |
32 | var frameSelected = function(context) {
33 | // Filter selections for artboards
34 | var predicate = NSPredicate.predicateWithFormat("className == %@","MSArtboardGroup"),
35 | selection = context.selection.filteredArrayUsingPredicate(predicate);
36 |
37 | // If artboards are selected...
38 | if (selection.count() > 0) {
39 | // Frame the artboards
40 | createFrames(context,selection);
41 |
42 | if (!debugMode) googleAnalytics(context,"frameSelected","run");
43 | }
44 | // If no artboards are selected...
45 | else {
46 | // Display feedback
47 | displayDialog("Select at least one artboard.",pluginName);
48 | }
49 | }
50 |
51 | var report = function(context) {
52 | openUrl("https://github.com/sonburn/artboard-framer/issues/new");
53 | if (!debugMode) googleAnalytics(context,"report","report");
54 | }
55 |
56 | var plugins = function(context) {
57 | openUrl("https://sonburn.github.io/");
58 |
59 | if (!debugMode) googleAnalytics(context,"plugins","plugins");
60 | }
61 |
62 | var donate = function(context) {
63 | openUrl("https://www.paypal.me/sonburn");
64 | if (!debugMode) googleAnalytics(context,"donate","donate");
65 | }
66 |
67 | function actionWithType(context,type) {
68 | var controller = context.document.actionsController();
69 |
70 | if (controller.actionWithName) {
71 | return controller.actionWithName(type);
72 | } else if (controller.actionWithID) {
73 | return controller.actionWithID(type);
74 | } else {
75 | return controller.actionForID(type);
76 | }
77 | }
78 |
79 | function createAlertWindow(context,name,text) {
80 | var alertWindow = COSAlertWindow.new();
81 |
82 | var iconPath = context.plugin.urlForResourceNamed("icon.png").path(),
83 | icon = NSImage.alloc().initByReferencingFile(iconPath);
84 |
85 | alertWindow.setIcon(icon);
86 | alertWindow.setMessageText(name);
87 | alertWindow.setInformativeText(text);
88 |
89 | return alertWindow;
90 | }
91 |
92 | function createFrames(context,artboards) {
93 | // Set the frame library
94 | frameLibrary = getFrameLibrary(context);
95 |
96 | // Get the frame settings
97 | var frameSettings = getFrameSettings(context),
98 | frameSymbol = frameSettings.frame;
99 |
100 | // If a frame has been selected...
101 | if (frameSymbol) {
102 | // Set the frame group
103 | frameGroup = getFrameGroup(context);
104 |
105 | // Artboard & frame variables
106 | var artboardLoop = artboards.objectEnumerator(),
107 | artboard,
108 | frameSymbol = importForeignSymbol(frameSymbol,frameLibrary).symbolMaster(),
109 | framesAdded = 0,
110 | framesUpdated = 0,
111 | framesRemoved = removeOrphans(context);
112 |
113 | // Iterate through the artboards...
114 | while (artboard = artboardLoop.nextObject()) {
115 | // Check for existing frame for the artboard
116 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@).linkedToArtboard == %@",pluginIdentifier,artboard.objectID()),
117 | artboardFrame = frameGroup.layers().filteredArrayUsingPredicate(predicate).firstObject();
118 |
119 | // If frame does not exist...
120 | if (!artboardFrame) {
121 | // Create new frame and determine where to insert
122 | var artboardFrame = frameSymbol.newSymbolInstance();
123 |
124 | // Link frame to artboard
125 | context.command.setValue_forKey_onLayer(artboard.objectID(),"linkedToArtboard",artboardFrame);
126 |
127 | // Insert frame into frame group
128 | frameGroup.insertLayer_atIndex(artboardFrame,null);
129 |
130 | // Increment the appropriate frame counter
131 | framesAdded++;
132 | }
133 | // If frame exists...
134 | else {
135 | // Change the existing frame
136 | artboardFrame.changeInstanceToSymbol(frameSymbol);
137 |
138 | // Resize the frame to the new master
139 | artboardFrame.resetSizeToMaster();
140 |
141 | // Update the name of the frame instance
142 | artboardFrame.setName(frameSymbol.name());
143 |
144 | // Increment the appropriate frame counter
145 | framesUpdated++;
146 | }
147 | }
148 |
149 | // Update all frame positions
150 | updateFrames(context,frameGroup);
151 |
152 | // If user wants a frame slice
153 | if (frameSettings.slice) {
154 | // Frame slice variables
155 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@).frameSlice == true",pluginIdentifier),
156 | frameSlice = context.document.currentPage().children().filteredArrayUsingPredicate(predicate).firstObject(),
157 | frameSlicePad = 200;
158 |
159 | // If frame slice does not exist...
160 | if (!frameSlice) {
161 | // Create the slice
162 | var sliceLayer = MSSliceLayer.new();
163 | sliceLayer.setName("Artboard Framer");
164 |
165 | // Designate the slice as the frameSlice
166 | context.command.setValue_forKey_onLayer(true,"frameSlice",sliceLayer);
167 |
168 | // Insert slice into frame group
169 | frameGroup.insertLayer_atIndex(sliceLayer,0);
170 | }
171 |
172 | // Set the frame slice location and dimensions
173 | sliceLayer.frame().setX(0 - frameSlicePad);
174 | sliceLayer.frame().setY(0 - frameSlicePad);
175 | sliceLayer.frame().setWidth(frameGroup.frame().width() + frameSlicePad * 2);
176 | sliceLayer.frame().setHeight(frameGroup.frame().height() + frameSlicePad * 2);
177 |
178 | // Resize the frame group
179 | if (sketch.version.sketch > 52) {
180 | frameGroup.fixGeometryWithOptions(0);
181 | } else {
182 | frameGroup.resizeToFitChildrenWithOption(0);
183 | }
184 | }
185 |
186 | // Display feedback
187 | context.document.showMessage((framesAdded + framesUpdated) + " artboards have been framed");
188 | }
189 | }
190 |
191 | function createCheckbox(item,state,frame) {
192 | var checkbox = NSButton.alloc().initWithFrame(frame),
193 | state = (state == false) ? NSOffState : NSOnState;
194 |
195 | checkbox.setButtonType(NSSwitchButton);
196 | checkbox.setBezelStyle(0);
197 | checkbox.setTitle(item.name);
198 | checkbox.setTag(item.value);
199 | checkbox.setState(state);
200 |
201 | return checkbox;
202 | }
203 |
204 | function createSelect(items,selected,frame) {
205 | var comboBox = NSComboBox.alloc().initWithFrame(frame),
206 | selected = (selected > -1) ? selected : 0;
207 |
208 | comboBox.addItemsWithObjectValues(items);
209 | comboBox.selectItemAtIndex(selected);
210 | comboBox.setNumberOfVisibleItems(16);
211 | comboBox.setCompletes(1);
212 |
213 | return comboBox;
214 | }
215 |
216 | function displayDialog(message,title) {
217 | NSApplication.sharedApplication().displayDialog_withTitle(message,title);
218 | }
219 |
220 | function getFrameGroup(context) {
221 | var predicate = NSPredicate.predicateWithFormat("userInfo != nil && function(userInfo,'valueForKeyPath:',%@).frameGroup == true",pluginIdentifier),
222 | frameGroup = context.document.currentPage().children().filteredArrayUsingPredicate(predicate).firstObject();
223 |
224 | if (!frameGroup) {
225 | var frameGroup = MSLayerGroup.new();
226 |
227 | frameGroup.setName("Frames");
228 | frameGroup.setHasClickThrough(true);
229 | frameGroup.frame().setX(0);
230 | frameGroup.frame().setY(0);
231 |
232 | context.command.setValue_forKey_onLayer(true,"frameGroup",frameGroup);
233 |
234 | context.document.currentPage().insertLayer_atIndex(frameGroup,0);
235 | }
236 |
237 | return frameGroup;
238 | }
239 |
240 | function getFrameLibrary(context) {
241 | // Get the frame library
242 | var predicate = NSPredicate.predicateWithFormat("libraryID == %@",frameLibraryID),
243 | frameLibrary = AppController.sharedInstance().librariesController().libraries().filteredArrayUsingPredicate(predicate).firstObject(),
244 | frameLibraryName = "Artboard Framer.sketch",
245 | frameLibraryPath = context.plugin.urlForResourceNamed(frameLibraryName).path();
246 |
247 | // If the frame library exists and it's disabled...
248 | if (frameLibrary && frameLibrary.enabled() == 0) {
249 | // Enable the frame library
250 | frameLibrary.setEnabled(1);
251 | }
252 |
253 | // If the frame library does not exist...
254 | if (!frameLibrary) {
255 | // Get file URL of frame library in plugin bundle
256 | var fileURLWithPath = NSURL.fileURLWithPath(frameLibraryPath);
257 |
258 | // Add the frame library to Sketch
259 | NSApp.delegate().librariesController().addAssetLibraryAtURL(fileURLWithPath);
260 |
261 | // Alert Sketch of library change
262 | AppController.sharedInstance().librariesController().notifyLibraryChange(null);
263 |
264 | // Display feedback
265 | context.document.showMessage("Artboard Framer library installed");
266 |
267 | // Get the frame library
268 | frameLibrary = AppController.sharedInstance().librariesController().libraries().filteredArrayUsingPredicate(predicate).firstObject();
269 | }
270 |
271 | // Return the frame library
272 | return frameLibrary;
273 | }
274 |
275 | function getFrameSettings(context) {
276 | // Get the frame symbols & names
277 | var libraryData = getLibraryData(context,frameLibrary),
278 | frameUsedLast = context.command.valueForKey_onLayer("frameUsedLast",context.document.documentData()),
279 | frameSymbol = (frameUsedLast) ? getObjectByID(libraryData.librarySymbols,frameUsedLast) : null,
280 | typeNames = libraryData.libraryPages.valueForKey("name"),
281 | typeNameSelect = (frameUsedLast) ? typeNames.indexOfObject(String(frameSymbol.parentPage().name())) : 0,
282 | predicate = NSPredicate.predicateWithFormat("parentPage.name == %@",typeNames[typeNameSelect]),
283 | frameSymbols = libraryData.librarySymbols.filteredArrayUsingPredicate(predicate),
284 | frameSymbolNames = (frameSymbols) ? frameSymbols.valueForKey("name") : ["No frames of this type"],
285 | frameSymbolSelect = (frameUsedLast) ? frameSymbols.indexOfObject(frameSymbol) : 0;
286 |
287 | // Default settings
288 | var defaultSettings = {};
289 | defaultSettings.artboardShadowState = 1;
290 | defaultSettings.frameSliceState = 0;
291 |
292 | // Update default settings with cached settings
293 | defaultSettings = getSettings(context,context.document.documentData(),defaultSettings,pluginIdentifier);
294 |
295 | // Create the alert window
296 | var alertWindow = createAlertWindow(context,pluginName,pluginDescription);
297 |
298 | // Create the type select, and add to alert window
299 | var typeSelect = createSelect(typeNames,typeNameSelect,NSMakeRect(0,0,300,28));
300 | alertWindow.addAccessoryView(typeSelect);
301 |
302 | // Create the type select delegate
303 | var typeSelectDelegate = new MochaJSDelegate({
304 | "comboBoxSelectionDidChange:" : (function(sender) {
305 | // Get the frame symbols & names
306 | var selectedType = sender.object().objectValueOfSelectedItem();
307 | predicate = NSPredicate.predicateWithFormat("parentPage.name == %@",selectedType);
308 | frameSymbols = libraryData.librarySymbols.filteredArrayUsingPredicate(predicate);
309 | frameSymbolNames = frameSymbols.valueForKey("name");
310 |
311 | // Empty and populate the type select
312 | frameSelect.removeAllItems();
313 | frameSelect.addItemsWithObjectValues(frameSymbolNames);
314 | frameSelect.selectItemAtIndex(0);
315 |
316 | // If there are frame symbols...
317 | if (frameSymbols) {
318 | // Disable form elements
319 | frameSelect.setEnabled(1);
320 | buttonOK.setEnabled(1);
321 | }
322 | // If there are no frame symbols...
323 | else {
324 | // Enable form elements
325 | frameSelect.setEnabled(0);
326 | buttonOK.setEnabled(0);
327 | }
328 | })
329 | });
330 |
331 | // Append the delegate to the type select
332 | typeSelect.setDelegate(typeSelectDelegate.getClassInstance());
333 |
334 | // Create the frame select, and add to alert window
335 | var frameSelect = createSelect(frameSymbolNames,frameSymbolSelect,NSMakeRect(0,0,300,28));
336 | alertWindow.addAccessoryView(frameSelect);
337 |
338 | // Create the artboard shadow checkbox, and add to alert window
339 | var shadowToggle = createCheckbox({name:"Disable artboard shadows",value:1},defaultSettings.artboardShadowState,NSMakeRect(0,0,300,18));
340 | alertWindow.addAccessoryView(shadowToggle);
341 |
342 | // Create the slice checkbox, and add to alert window
343 | var frameSlice = createCheckbox({name:"Create slice around frames",value:1},defaultSettings.frameSliceState,NSMakeRect(0,0,300,18));
344 | alertWindow.addAccessoryView(frameSlice);
345 |
346 | // Buttons for alert window
347 | var buttonOK = alertWindow.addButtonWithTitle("OK");
348 | var buttonCancel = alertWindow.addButtonWithTitle("Cancel");
349 |
350 | // If there are no frame symbols...
351 | if (!frameSymbols) {
352 | // Disable form elements
353 | frameSelect.setEnabled(0);
354 | buttonOK.setEnabled(0);
355 | }
356 |
357 | // Set key order
358 | setKeyOrder(alertWindow,[
359 | typeSelect,
360 | frameSelect,
361 | shadowToggle,
362 | frameSlice,
363 | buttonOK
364 | ]);
365 |
366 | // Display the alert window and capture the response
367 | var alertResponse = alertWindow.runModal();
368 |
369 | // If user tapped OK button...
370 | if (alertResponse == 1000) {
371 | // Get current artboard shadow setting
372 | var showArtboardShadow = NSUserDefaults.standardUserDefaults().boolForKey("showArtboardShadow");
373 |
374 | // If the current setting and shadowToggle checkbox state conflict...
375 | if (shadowToggle.state() == 1 && showArtboardShadow == 1 || shadowToggle.state() == 0 && showArtboardShadow == 0) {
376 | // Toggle the artboard shadows
377 | actionWithType(context,"MSToggleArtboardShadowAction").toggleArtboardShadow(null);
378 | }
379 |
380 | // Remember the selections for future use
381 | context.command.setValue_forKey_onLayer(frameSymbols[frameSelect.indexOfSelectedItem()].objectID(),"frameUsedLast",context.document.documentData());
382 | context.command.setValue_forKey_onLayer(shadowToggle.state(),"artboardShadowState",context.document.documentData());
383 | context.command.setValue_forKey_onLayer(frameSlice.state(),"frameSliceState",context.document.documentData());
384 |
385 | // Return the selected frame
386 | return {
387 | frame : frameSymbols[frameSelect.indexOfSelectedItem()],
388 | slice : frameSlice.state()
389 | };
390 | } else return false;
391 | }
392 |
393 | function getLibraryData(context,library) {
394 | var libraryPath = NSURL.fileURLWithPath(library.locationOnDisk().path()),
395 | libraryFile = openFile(libraryPath),
396 | libraryPages = libraryFile.documentData().pages(),
397 | librarySymbols = libraryFile.documentData().allSymbols(),
398 | librarySymbolSort = NSSortDescriptor.sortDescriptorWithKey_ascending("name",1);
399 |
400 | libraryFile.close();
401 |
402 | libraryPages.removeObjectAtIndex(0);
403 |
404 | return {
405 | libraryPages : libraryPages,
406 | librarySymbols : librarySymbols.sortedArrayUsingDescriptors([librarySymbolSort])
407 | }
408 | }
409 |
410 | function getObjectByID(source,objectID) {
411 | var predicate = NSPredicate.predicateWithFormat("objectID == %@",objectID);
412 |
413 | return source.filteredArrayUsingPredicate(predicate).firstObject();
414 | }
415 |
416 | function getSettings(context,location,settings) {
417 | try {
418 | for (i in settings) {
419 | var value = context.command.valueForKey_onLayer(i,location);
420 | if (value) settings[i] = value;
421 | }
422 |
423 | return settings;
424 | } catch(err) {
425 | log("Unable to fetch settings");
426 | }
427 | }
428 |
429 | function googleAnalytics(context,category,action,label,value) {
430 | var trackingID = "UA-117546302-1",
431 | uuidKey = "google.analytics.uuid",
432 | uuid = NSUserDefaults.standardUserDefaults().objectForKey(uuidKey);
433 |
434 | if (!uuid) {
435 | uuid = NSUUID.UUID().UUIDString();
436 | NSUserDefaults.standardUserDefaults().setObject_forKey(uuid,uuidKey);
437 | }
438 |
439 | var url = "https://www.google-analytics.com/collect?v=1";
440 | // Tracking ID
441 | url += "&tid=" + trackingID;
442 | // Source
443 | url += "&ds=sketch" + sketch.version.sketch;
444 | // Client ID
445 | url += "&cid=" + uuid;
446 | // pageview, screenview, event, transaction, item, social, exception, timing
447 | url += "&t=event";
448 | // App Name
449 | url += "&an=" + encodeURI(context.plugin.name());
450 | // App ID
451 | url += "&aid=" + context.plugin.identifier();
452 | // App Version
453 | url += "&av=" + context.plugin.version();
454 | // Event category
455 | url += "&ec=" + encodeURI(category);
456 | // Event action
457 | url += "&ea=" + encodeURI(action);
458 | // Event label
459 | if (label) {
460 | url += "&el=" + encodeURI(label);
461 | }
462 | // Event value
463 | if (value) {
464 | url += "&ev=" + encodeURI(value);
465 | }
466 |
467 | var session = NSURLSession.sharedSession(),
468 | task = session.dataTaskWithURL(NSURL.URLWithString(NSString.stringWithString(url)));
469 |
470 | task.resume();
471 | }
472 |
473 | function importForeignSymbol(symbol,library) {
474 | var intoDocument = MSDocument.currentDocument().documentData(),
475 | libraryController = AppController.sharedInstance().librariesController(),
476 | foreignSymbol;
477 |
478 | if (sketch.version.sketch >= 50) {
479 | var objectReference = MSShareableObjectReference.referenceForShareableObject_inLibrary(symbol,library);
480 |
481 | foreignSymbol = libraryController.importShareableObjectReference_intoDocument(objectReference,intoDocument);
482 | } else {
483 | foreignSymbol = libraryController.importForeignSymbol_fromLibrary_intoDocument_(symbol,library,intoDocument);
484 | }
485 |
486 | return foreignSymbol;
487 | }
488 |
489 | function openFile(path) {
490 | var file = MSDocument.new();
491 |
492 | return (file.readFromURL_ofType_error(path,'com.bohemiancoding.sketch.drawing',nil)) ? file : nil;
493 | }
494 |
495 | function openUrl(url) {
496 | NSWorkspace.sharedWorkspace().openURL(NSURL.URLWithString(url));
497 | }
498 |
499 | function removeOrphans(context) {
500 | var frames = frameGroup.layers(),
501 | framesRemoved = 0;
502 |
503 | if (frames) {
504 | for (var i = 0; i < frames.length; i++) {
505 | var linkedToArtboard = context.command.valueForKey_onLayer("linkedToArtboard",frames[i]),
506 | linkedArtboard = context.document.documentData().artboardWithID(linkedToArtboard);
507 |
508 | if (!linkedArtboard) {
509 | frames[i].removeFromParent();
510 |
511 | framesRemoved++;
512 | }
513 | }
514 | }
515 |
516 | return framesRemoved;
517 | }
518 |
519 | function setKeyOrder(alert,order) {
520 | for (var i = 0; i < order.length; i++) {
521 | var thisItem = order[i],
522 | nextItem = order[i+1];
523 |
524 | if (nextItem) thisItem.setNextKeyView(nextItem);
525 | }
526 |
527 | alert.alert().window().setInitialFirstResponder(order[0]);
528 | }
529 |
530 | function updateFrames(context) {
531 | var frames = frameGroup.layers(),
532 | frameLoop = frames.objectEnumerator(),
533 | frame;
534 |
535 | while (frame = frameLoop.nextObject()) {
536 | var frameSymbol = frame.symbolMaster(),
537 | linkedToArtboard = context.command.valueForKey_onLayer("linkedToArtboard",frame),
538 | linkedArtboard = context.document.documentData().artboardWithID(linkedToArtboard);
539 |
540 | frame.absoluteRect().setX(linkedArtboard.frame().x() + context.command.valueForKey_onLayer("offsetX",frameSymbol));
541 | frame.absoluteRect().setY(linkedArtboard.frame().y() + context.command.valueForKey_onLayer("offsetY",frameSymbol));
542 |
543 | if (context.command.valueForKey_onLayer("canResize",frameSymbol)) {
544 | frame.frame().setWidth(linkedArtboard.frame().width() - context.command.valueForKey_onLayer("offsetX",frameSymbol))
545 | frame.frame().setHeight(linkedArtboard.frame().height() - context.command.valueForKey_onLayer("offsetY",frameSymbol))
546 | }
547 | }
548 |
549 | if (sketch.version.sketch > 52) {
550 | frameGroup.fixGeometryWithOptions(0);
551 | } else {
552 | frameGroup.resizeToFitChildrenWithOption(0);
553 | }
554 | }
555 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Jason Burns (Sonburn)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | Frame your artboards with a device image or wireframe.
4 |
5 | 
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | # Demonstration
16 |
17 | [](https://www.youtube.com/watch?v=e-USy4qAxCw)
18 |
19 | # Installation
20 |
21 | ## Automatic
22 | Search for Artboard Framer in [Sketchrunner](http://sketchrunner.com/) or [Sketch Toolbox](http://sketchtoolbox.com/) if you have one of those installed.
23 |
24 | Once installed, Sketch will automatically notify you when an update is available (version 0.1 and later).
25 |
26 | ## Manual
27 |
28 | 1. Download and open artboard-framer-master.zip
29 | 2. Navigate to Artboard Framer.sketchplugin and copy/move to your plugins directory
30 |
31 | To find your plugins directory...
32 |
33 | 1. In the Sketch menu, navigate to Plugins > Manage Plugins...
34 | 2. Click the cog in the lower left of the plugins window, and click Reveal Plugins Folder
35 |
36 | # Changelog
37 |
38 | * **0.13** - Fix for plugin window not appearing. Fix for Sketch 72.
39 | * **0.12** - Fixed link for Other Plugins.
40 | * **0.11** - Fix for Sketch 53.
41 | * **0.10** - Utilizing MSShareableObjectReference for importing in Sketch 50, reinstated old method for previous versions of Sketch.
42 | * **0.9** - Fix for localSymbolForSymbol_inLibrary being deprecated in Sketch 50.
43 | * **0.8** - Added plugin icon to manifest for Sketch 50.
44 | * **0.7** - Added web browser frames which resize to the artboards for which they are applied.
45 | * **0.6** - Added iPhone X and Samsung Galaxy S8.
46 | * **0.5** - Added functionality to create a slice around artboard frames.
47 | * **0.4** - Added some tablet devices and iPhone SE, and improved device selection & information in plugin dialog.
48 | * **0.3** - Added new devices for iPhone Plus and Sony TV.
49 | * **0.2** - Fix for using different frame types on the same page.
50 | * **0.1** - Initial commit.
51 |
52 | # To-Do
53 |
54 | * Host device library for easier updating.
55 | * Add landscape mode phone and tablet devices.
56 | * Add watch devices.
57 | * Add web devices.
58 | * Add more modern devices.
59 | * Pre-select type for artboard size(s).
60 |
61 | # Contact
62 |
63 | Find me on Twitter
64 |
65 | # Support
66 |
67 | If you find this plugin helpful, or would like to support my plugins in general, buy me ☕️ via PayPal.
68 |
69 | # License
70 |
71 | Copyright (c) 2021 Jason Burns (Sonburn). See LICENSE.md for further details.
72 |
--------------------------------------------------------------------------------
/appcast.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Artboard Framer
5 | http://sparkle-project.org/files/sparkletestcast.xml
6 | Frame your artboards with a device image or wireframe.
7 | en
8 | -
9 | Version 0.13
10 |
11 |
13 | Fix for plugin window not appearing. Fix for Sketch 72.
14 |
15 | ]]>
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/artboard-framer/2e9087e73dccddfbef56c1a02bcc005ca1c02fde/images/logo.png
--------------------------------------------------------------------------------
/images/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonburn/artboard-framer/2e9087e73dccddfbef56c1a02bcc005ca1c02fde/images/screenshot.png
--------------------------------------------------------------------------------