├── .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 | ![Artboard Framer](https://raw.githubusercontent.com/sonburn/artboard-framer/master/images/logo.png) 2 | 3 | Frame your artboards with a device image or wireframe. 4 | 5 | ![Artboard Framer](https://raw.githubusercontent.com/sonburn/artboard-framer/master/images/screenshot.png) 6 | 7 | 8 | runner-badge-blue 9 | 10 | 11 | 12 | 13 | 14 | 15 | # Demonstration 16 | 17 | [![Artboard Framer Demonstration](https://img.youtube.com/vi/e-USy4qAxCw/0.jpg)](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 @sonburn 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 --------------------------------------------------------------------------------