├── LICENSE ├── PS Guides - 3.2.1.zip ├── PS Guides.sketchplugin └── Contents │ ├── Resources │ ├── PS-Guides.png │ ├── add-guides.png │ ├── alert.mp3 │ ├── clear-guides.png │ └── user.config │ └── Sketch │ ├── functions.js │ ├── handler.js │ ├── help.js │ ├── main.js │ └── manifest.json ├── README.md ├── _config.yml └── appcast.xml /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Pratik Shah 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 | -------------------------------------------------------------------------------- /PS Guides - 3.2.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pratikjshah/Sketch-Guides/93c64cd19dd14b43f7d4ce48241e43f41fbc253c/PS Guides - 3.2.1.zip -------------------------------------------------------------------------------- /PS Guides.sketchplugin/Contents/Resources/PS-Guides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pratikjshah/Sketch-Guides/93c64cd19dd14b43f7d4ce48241e43f41fbc253c/PS Guides.sketchplugin/Contents/Resources/PS-Guides.png -------------------------------------------------------------------------------- /PS Guides.sketchplugin/Contents/Resources/add-guides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pratikjshah/Sketch-Guides/93c64cd19dd14b43f7d4ce48241e43f41fbc253c/PS Guides.sketchplugin/Contents/Resources/add-guides.png -------------------------------------------------------------------------------- /PS Guides.sketchplugin/Contents/Resources/alert.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pratikjshah/Sketch-Guides/93c64cd19dd14b43f7d4ce48241e43f41fbc253c/PS Guides.sketchplugin/Contents/Resources/alert.mp3 -------------------------------------------------------------------------------- /PS Guides.sketchplugin/Contents/Resources/clear-guides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pratikjshah/Sketch-Guides/93c64cd19dd14b43f7d4ce48241e43f41fbc253c/PS Guides.sketchplugin/Contents/Resources/clear-guides.png -------------------------------------------------------------------------------- /PS Guides.sketchplugin/Contents/Resources/user.config: -------------------------------------------------------------------------------- 1 | {"localUpdateTime":1547792675473,"localVersion":"3.2.1","guidesConfig":{"column":4,"gutter":24,"lOffset":0,"rOffset":0}} -------------------------------------------------------------------------------- /PS Guides.sketchplugin/Contents/Sketch/functions.js: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------- 2 | 3 | author: Pratik Shah 4 | Homepage: https://github.com/pratikjshah/PS-Guides 5 | license: MIT 6 | 7 | ----------------------------------------------------------*/ 8 | 9 | @import "main.js" 10 | 11 | function setGuides(selectedLayers) { 12 | 13 | // Load the window 14 | var window = createGuidesWindow(configData.guidesConfig.column, configData.guidesConfig.gutter, configData.guidesConfig.lOffset, configData.guidesConfig.rOffset); 15 | var alert = window[0]; 16 | 17 | // When “Ok” is clicked 18 | var response = alert.runModal(); 19 | if (response == "1000") { 20 | 21 | // Get the user’s values 22 | // The replace strips any non-numeric (and non-slash for radius) characters 23 | // e.g. `px` 24 | var column = parseInt(columnTextfield.stringValue().replace(/[^0-9.,]/g, '')); 25 | var gutter = parseInt(gutterTextfield.stringValue().replace(/[^0-9.,]/g, '')); 26 | var lOffset = parseInt(lOffsetTextfield.stringValue().replace(/[^0-9.,]/g, '')); 27 | var rOffset = parseInt(rOffsetTextfield.stringValue().replace(/[^0-9.,]/g, '')); 28 | 29 | drawGuides(selectedLayers, column, gutter, lOffset, rOffset); 30 | 31 | } 32 | } 33 | 34 | function getLayerProps (selectedLayers) { 35 | var rect; 36 | var selectedCount = selectedLayers.count(); 37 | if (selectedCount == 1) { 38 | var layer = selectedLayers.firstObject(); 39 | rect = layer.absoluteRect(); 40 | return { 41 | x: Math.round(rect.x()), 42 | y: Math.round(rect.y()), 43 | width: Math.round(rect.width()), 44 | height: Math.round(rect.height()), 45 | maxX: Math.round(rect.x() + rect.width()), 46 | maxY: Math.round(rect.y() + rect.height()), 47 | } 48 | } 49 | } 50 | 51 | function addVGuide(position) { 52 | var artBoardHRuler = artboard.horizontalRulerData(); 53 | artBoardHRuler.addGuideWithValue(position); 54 | } 55 | 56 | function drawGuides(selectedLayers, column, gutter, lOffset, rOffset) { 57 | 58 | var selectedLayerProps = getLayerProps(selectedLayers); 59 | 60 | //globalContext.document.showMessage("Column: "+column+" | Gutter: "+gutter + " | Layer: "+JSON.stringify(selectedLayerProps)); 61 | var columnWidth = Math.round((selectedLayerProps.width - lOffset - rOffset - Math.round((gutter*(column-1))))/column); 62 | var artboardX = artboard.frame().x(); 63 | var startPosition = ((artboardX - selectedLayerProps.x)*-1) + lOffset; 64 | 65 | //globalContext.document.showMessage("columnWidth: "+columnWidth+" | startPosition: "+startPosition); 66 | 67 | var frameCenter = startPosition + Math.round(selectedLayerProps.width / 2); 68 | var gutterCount = column + 1; 69 | var spaceForColumn = selectedLayerProps.width - (gutterCount * gutter); 70 | var rulerCount = column*2 + 2; 71 | var drawGuideAt = frameCenter - Math.round(selectedLayerProps.width / 2) - gutter; 72 | var total = (columnWidth*column)+(gutter*(gutterCount-2)); 73 | //globalContext.document.showMessage("Total: "+total+" | selection width: "+selectedLayerProps.width+" | gap: " + (selectedLayerProps.width - total)); 74 | 75 | if (total != parseInt(selectedLayerProps.width)) { 76 | 77 | var count = total + lOffset + rOffset - selectedLayerProps.width; 78 | var action = "⬅️stretched➡️"; 79 | if (count < 0) { 80 | count = count*-1; 81 | action = "➡️shrunk⬅️"; 82 | } 83 | 84 | var msg = "Guides area was " + action + " by "+ count +"px on right!"; 85 | 86 | if (count != 0) { 87 | globalContext.document.showMessage(msg); 88 | } 89 | } 90 | 91 | /*if (total != parseInt(selectedLayerProps.width)) { 92 | var message = "It is not possible to divide selected width of "+selectedLayerProps.width+" into "+column+" equal columns and gutter of "+gutter; 93 | var confirmButtonText = "Change config"; 94 | var cancelButtonText = "Draw anyway"; 95 | //createErrorWindow(message,confirmButtonText,cancelButtonText); 96 | var errorWindow = createErrorWindow(message,confirmButtonText,cancelButtonText); 97 | var errorAlert = errorWindow[0]; 98 | // When “Ok” is clicked 99 | var errorResponse = errorAlert.runModal(); 100 | //log("errorResponse: "+errorResponse); 101 | //globalContext.showMessage("errorResponse: "+errorResponse); 102 | if (errorResponse == "1000") { 103 | addGuides(globalContext); 104 | return; 105 | } else if (errorResponse == "1001") { 106 | plotGuides(drawGuideAt,column,gutter,columnWidth,rulerCount,lOffset,rOffset); 107 | } 108 | return; 109 | }*/ 110 | 111 | plotGuides(drawGuideAt,column,gutter,columnWidth,rulerCount,lOffset,rOffset); 112 | } 113 | 114 | function plotGuides(drawGuideAt,column,gutter,columnWidth,rulerCount,lOffset,rOffset) { 115 | // Clear old guides 116 | //removeAllGuides(); 117 | //clearGuides(globalContext); 118 | removeVerticalGuides(); 119 | removeHorizontalGuides(); 120 | 121 | //addVGuide(drawGuideAt); 122 | for (var i=0; i < rulerCount; i++) { 123 | addVGuide(drawGuideAt); 124 | if ((i % 2) == 0) { 125 | drawGuideAt = drawGuideAt + gutter; 126 | } else { 127 | drawGuideAt = drawGuideAt + columnWidth 128 | } 129 | } 130 | 131 | configData.guidesConfig.column = column; 132 | configData.guidesConfig.gutter = gutter; 133 | configData.guidesConfig.lOffset = lOffset; 134 | configData.guidesConfig.rOffset = rOffset; 135 | saveData(configData); 136 | 137 | /* 138 | if (!globalContext.document.isRulersVisible()) { 139 | globalContext.document.toggleRulers(); 140 | } 141 | */ 142 | if (!doc.isRulersVisible()) { 143 | globalContext.document.actionsController().actionForID("MSToggleRulersAction").toggleRulers(nil); 144 | } 145 | } 146 | 147 | function removeHorizontalGuides() { 148 | var doc = globalContext.document; 149 | var page = doc.currentPage(); 150 | var artBoard = page.currentArtboard(); 151 | removeGuides(artBoard.horizontalRulerData()); 152 | } 153 | function removeVerticalGuides() { 154 | var doc = globalContext.document; 155 | var page = doc.currentPage(); 156 | var artBoard = page.currentArtboard(); 157 | removeGuides(artBoard.verticalRulerData()); 158 | } 159 | function removeGuides(guideData) { 160 | while (guideData.numberOfGuides() > 0) { 161 | guideData.removeGuideAtIndex(0); 162 | } 163 | } 164 | 165 | function isSelectionAllowed(selectionType) { 166 | var allowedTypes = ["MSShapeGroup", "MSRectangleShape", "MSOvalShape", "MSTriangleShape", "MSShapePathLayer", "MSStarShape", "MSPolygonShape", "MSHotspotLayer", "MSTextLayer", "MSLayerGroup", "MSArtboardGroup", "MSSymbolMaster", "MSBitmapLayer", "MSSliceLayer", "MSSymbolInstance"]; 167 | //var index = allowedTypes.indexOf(selectionType); 168 | //return index; 169 | 170 | for (var j=0; j 0) { 178 | //if (data >= 0) { 179 | return 1; 180 | } 181 | return 0; 182 | } 183 | 184 | function hasParentArtboard(layer) { 185 | // Check if any layers were passed in to the function 186 | if (layer == undefined) { 187 | return -1; 188 | } 189 | 190 | var currentLayer = layer; 191 | 192 | while (true) { 193 | var className = currentLayer.className(); 194 | if (className == 'MSPage') { 195 | //If it's an MSPage, then there was no artboard selected to begin with 196 | return 2; 197 | } else if (className == 'MSArtboardGroup' || className == 'MSSymbolMaster') { 198 | //If it's an MSArtboardGroup, we've found it and we can just return the currentLayer. 199 | return 1; 200 | } else { 201 | //Otherwise, we're still nested deep inside an artboard 202 | currentLayer = currentLayer.parentGroup(); 203 | } 204 | } 205 | return -2; 206 | } 207 | 208 | function showErrorAlertWithSound(msg) { 209 | var errorAlertFile = plugin.urlForResourceNamed("alert.mp3").path(); 210 | var sound = NSSound.alloc().initWithContentsOfFile_byReference(errorAlertFile,true); 211 | sound.play(); 212 | globalContext.document.showMessage(msg); 213 | } 214 | 215 | function isSelectionValid(selectedLayers) { 216 | //globalContext.document.showMessage("IN isSelectionValid"); 217 | var selectedCount = selectedLayers.count(); 218 | var msg = ""; 219 | 220 | if (selectedCount <= 0) { 221 | msg = "Please select at least one element."; 222 | showErrorAlertWithSound(msg); 223 | return false; 224 | } 225 | 226 | var layer = selectedLayers.firstObject(); 227 | var layerType = layer.className(); 228 | var hasArtboard = hasParentArtboard(layer); 229 | 230 | if (hasArtboard < 0) { 231 | msg = "Soemthing is wrong"; 232 | showErrorAlertWithSound(msg); 233 | return false; 234 | } else if (hasArtboard == 2) { 235 | msg = "Please select an element inside an Artboard."; 236 | showErrorAlertWithSound(msg); 237 | return false; 238 | } else if (isSelectionAllowed(layerType) < 0) { 239 | msg = "Currently "+layerType+" selection is not allowed."; 240 | showErrorAlertWithSound(msg); 241 | return false; 242 | } else if(selectedCount > 1) { 243 | msg = "Please select single layer"; 244 | showErrorAlertWithSound(msg); 245 | }else { 246 | return true; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /PS Guides.sketchplugin/Contents/Sketch/handler.js: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------- 2 | 3 | author: Pratik Shah 4 | Homepage: https://github.com/pratikjshah/PS-Guides 5 | license: MIT 6 | 7 | ----------------------------------------------------------*/ 8 | 9 | @import 'main.js' 10 | @import 'functions.js' 11 | 12 | var commandInit = function(context) { 13 | // Get all the things 14 | // context.document.showMessage("Inside commandInit"); 15 | // initPlugin(context); 16 | } 17 | 18 | var addGuides = function(context) { 19 | initPlugin(context); 20 | var selectedLayers = context.selection; 21 | 22 | if (isSelectionValid(selectedLayers)) { 23 | //var layer = selectedLayers.firstObject(); 24 | setGuides(selectedLayers); 25 | trackEvent("DrawGuides", "addGuides", 1); 26 | } 27 | } 28 | 29 | var addLastGuides = function(context) { 30 | initPlugin(context); 31 | var selectedLayers = context.selection; 32 | 33 | if (isSelectionValid(selectedLayers)) { 34 | //var layer = selectedLayers.firstObject(); 35 | drawGuides(selectedLayers, configData.guidesConfig.column, configData.guidesConfig.gutter, configData.guidesConfig.lOffset, configData.guidesConfig.rOffset); 36 | trackEvent("DrawGuides", "addLastGuides", 1); 37 | } 38 | } 39 | 40 | var clearGuides = function(context) { 41 | initPlugin(context); 42 | removeVerticalGuides(); 43 | removeHorizontalGuides(); 44 | trackEvent("ClearGuides", "bothGuides", 1); 45 | } 46 | -------------------------------------------------------------------------------- /PS Guides.sketchplugin/Contents/Sketch/help.js: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------- 2 | 3 | author: Pratik Shah 4 | Homepage: https://github.com/pratikjshah/PS-Guides 5 | license: MIT 6 | 7 | ----------------------------------------------------------*/ 8 | 9 | @import 'main.js' 10 | 11 | var reportIssue = function(context) { 12 | openUrlInBrowser("https://github.com/pratikjshah/PS-Guides/issues/"); 13 | } 14 | 15 | var aboutPratikShah = function(context) { 16 | openUrlInBrowser("http://pratikshah.website"); 17 | } 18 | 19 | 20 | function checkForUpdate(context) { 21 | initPlugin(context); 22 | 23 | var remoteManifest = getRemoteJson(remoteManifestUrl); 24 | //context.document.showMessage("remoteManifest: " + remoteManifest.version); 25 | trackEvent("checkForUpdate", "manualCheckForUpdate", 1); 26 | if (remoteManifest.version) { 27 | if (configData.localVersion == remoteManifest.version) { 28 | globalContext.document.showMessage("🤘Yo🤘! PS: Guides " + configData.localVersion + " is currently the newest version available."); 29 | } else { 30 | //globalContext.document.showMessage("need update:"); 31 | showAvailableUpdateDialog(); 32 | } 33 | } else { 34 | //globalContext.document.showMessage("can not check:"); 35 | showAvailableUpdateDialog(); 36 | } 37 | } 38 | 39 | function deselectAllLayers(context) { 40 | if(context.selection.count()) { 41 | context.selection.firstObject().select_byExtendingSelection(false, false); 42 | } 43 | } 44 | 45 | 46 | var collapseGroupsNArtboard = function (context) { 47 | initPlugin(context); 48 | var msg = "Failed to Collapse Groups & Artboards!"; 49 | 50 | var doc = context.document; 51 | var currentArtboard = doc.findCurrentArtboardGroup(); 52 | //doc.currentPage().deselectAllLayers(); 53 | deselectAllLayers(context); 54 | 55 | var action = doc.actionsController().actionForID("MSCollapseAllGroupsAction").collapseAllGroups(nil); 56 | trackEvent("TopUpEvents", "collapseGroupsNArtboard", 1); 57 | 58 | if(action.validate()) { 59 | action.doPerformAction(nil); 60 | currentArtboard.select_byExpandingSelection(true, false); 61 | } else { 62 | showErrorAlertWithSound(msg); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /PS Guides.sketchplugin/Contents/Sketch/main.js: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------- 2 | 3 | author: Pratik Shah 4 | Homepage: https://github.com/pratikjshah/PS-Guides 5 | license: MIT 6 | 7 | ----------------------------------------------------------*/ 8 | 9 | // Needed globally 10 | var globalContext; 11 | var doc; 12 | var page; 13 | var artboard; 14 | var plugin; 15 | var selection; 16 | var updateTime; 17 | var localDataPath; 18 | var configData; 19 | var remoteManifestUrl; 20 | 21 | // Initialise 22 | function initPlugin(context) { 23 | globalContext = context; 24 | doc = globalContext.document; 25 | page = doc.currentPage(); 26 | artboard = page.currentArtboard(); 27 | plugin = globalContext.plugin; 28 | selection = globalContext.selection; 29 | pluginRoot = globalContext.scriptPath.stringByDeletingLastPathComponent().stringByDeletingLastPathComponent().stringByDeletingLastPathComponent(); 30 | localDataPath = pluginRoot + "/Contents/Resources/user.config"; 31 | var currentVersion = globalContext.plugin.version() + ""; 32 | remoteManifestUrl = "https://raw.githubusercontent.com/pratikjshah/PS-Guides/master/PS%20Guides.sketchplugin/Contents/Sketch/manifest.json"; 33 | configData = readData(); 34 | //globalContext.document.showMessage(JSON.stringify(configData) + " | typeof: " + typeof(configData)); 35 | 36 | var newTime = new Date(); 37 | if (configData.localUpdateTime < newTime.getTime()) { 38 | //globalContext.document.showMessage("check for update:"); 39 | trackEvent("checkForUpdate", "dailyCheckForUpdate", 1); 40 | var remoteManifest = getRemoteJson(remoteManifestUrl); 41 | var localVersion = globalContext.plugin.version(); 42 | //globalContext.document.showMessage("remoteManifest: " + remoteManifest.version + " configData.localVersion: " + configData.localVersion); 43 | if (localVersion != remoteManifest.version) { 44 | globalContext.document.showMessage("📐 Guides:"+ localVersion + " is out of date! Please check for updates."); 45 | trackEvent("outDatedAlert", "dailyCheckForUpdate", 1); 46 | // console.log("📐 Guides:"+ configData.localVersion + " is out of date! Please check for updates."); 47 | // showAvailableUpdateDialog(context); 48 | } 49 | setUpdateCheckDayOnTomorrow(); 50 | } 51 | } 52 | 53 | // Utilities 54 | var utils = { 55 | "createLabel": function(frame, text) { 56 | var label = NSTextField.alloc().initWithFrame(frame); 57 | label.setStringValue(text); 58 | label.setSelectable(false); 59 | label.setEditable(false); 60 | label.setBezeled(false); 61 | label.setDrawsBackground(false); 62 | return label 63 | }, 64 | "getLayerProps": function() { 65 | var layer = selection.firstObject(); 66 | 67 | if (layer) { 68 | var x = layer.frame().x(); 69 | var y = layer.frame().y(); 70 | return [x, y]; 71 | } else { 72 | return [0, 0]; 73 | } 74 | } 75 | }; 76 | 77 | function createDownloadWindow() { 78 | // Setup the window 79 | var alert = COSAlertWindow.new(); 80 | alert.setIcon(NSImage.alloc().initByReferencingFile(plugin.urlForResourceNamed("PS-Guides.png").path())); 81 | alert.setMessageText("New version available"); 82 | alert.setInformativeText("Your current PS: Guides "+ configData.localVersion + " is out of date!"); 83 | alert.addButtonWithTitle("Download"); 84 | alert.addButtonWithTitle("Remind me tomorrow"); 85 | return [alert]; 86 | } 87 | 88 | function notAbletoCheckUpdateWindow() { 89 | // Setup the window 90 | var alert = COSAlertWindow.new(); 91 | alert.setIcon(NSImage.alloc().initByReferencingFile(plugin.urlForResourceNamed("PS-Guides.png").path())); 92 | alert.setMessageText("🙀Can not check for updates.🙀 Please visit the web site and download newest version.\n" + context.plugin.homepageURL()); 93 | alert.addButtonWithTitle("Download"); 94 | return [alert]; 95 | } 96 | 97 | function createErrorWindow(message,confirmButtonText,cancelButtonText) { 98 | // Setup the window 99 | var alert = COSAlertWindow.new(); 100 | alert.setIcon(NSImage.alloc().initByReferencingFile(plugin.urlForResourceNamed("PS-Guides.png").path())); 101 | alert.setMessageText(message) 102 | alert.addButtonWithTitle(confirmButtonText); 103 | alert.addButtonWithTitle(cancelButtonText); 104 | return [alert]; 105 | } 106 | 107 | function createGuidesWindow(column, gutter, lOffset, rOffset) { 108 | // Setup the window 109 | var alert = COSAlertWindow.new(); 110 | alert.setIcon(NSImage.alloc().initByReferencingFile(plugin.urlForResourceNamed("PS-Guides.png").path())); 111 | alert.setMessageText("Setup Guides") 112 | alert.addButtonWithTitle("Ok"); 113 | alert.addButtonWithTitle("Cancel"); 114 | 115 | // Create the main view 116 | var viewWidth = 300; 117 | var viewHeight = 100; 118 | var viewSpacer = 10; 119 | var view = NSView.alloc().initWithFrame(NSMakeRect(0, 0, viewWidth, viewHeight)); 120 | alert.addAccessoryView(view); 121 | 122 | // Create labels 123 | var columnLabel = utils.createLabel(NSMakeRect(0, viewHeight - 20, (viewWidth / 2) - viewSpacer, 20), "No. of Columns:"); 124 | var gutterLabel = utils.createLabel(NSMakeRect((viewWidth / 2) + viewSpacer, viewHeight - 20, (viewWidth / 2) - viewSpacer, 20), "Gutter space:"); 125 | var lOffsetLabel = utils.createLabel(NSMakeRect(0, viewHeight - 70, (viewWidth / 2) - viewSpacer, 20), "Left offset:"); 126 | var rOffsetLabel = utils.createLabel(NSMakeRect((viewWidth / 2) + viewSpacer, viewHeight - 70, (viewWidth / 2) - viewSpacer, 20), "Right offset:"); 127 | view.addSubview(columnLabel); 128 | view.addSubview(gutterLabel); 129 | view.addSubview(lOffsetLabel); 130 | view.addSubview(rOffsetLabel); 131 | 132 | // Create inputs 133 | columnTextfield = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 40, (viewWidth / 2) - viewSpacer, 20)); 134 | gutterTextfield = NSTextField.alloc().initWithFrame(NSMakeRect((viewWidth / 2) + viewSpacer, viewHeight - 40, (viewWidth / 2) - viewSpacer, 20)); 135 | lOffsetTextfield = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 90, (viewWidth / 2) - viewSpacer, 20)); 136 | rOffsetTextfield = NSTextField.alloc().initWithFrame(NSMakeRect((viewWidth / 2) + viewSpacer, viewHeight - 90, (viewWidth / 2) - viewSpacer, 20)); 137 | 138 | view.addSubview(columnTextfield); 139 | view.addSubview(gutterTextfield); 140 | view.addSubview(lOffsetTextfield); 141 | view.addSubview(rOffsetTextfield); 142 | 143 | // Allow tab to switch between inputs 144 | alert.alert().window().setInitialFirstResponder(columnTextfield); 145 | columnTextfield.setNextKeyView(gutterTextfield); 146 | gutterTextfield.setNextKeyView(lOffsetTextfield); 147 | lOffsetTextfield.setNextKeyView(rOffsetTextfield); 148 | rOffsetTextfield.setNextKeyView(columnTextfield); 149 | 150 | // Fill inputs 151 | var props = utils.getLayerProps(); 152 | columnTextfield.setStringValue(''+column); 153 | gutterTextfield.setStringValue(''+gutter); 154 | lOffsetTextfield.setStringValue(''+lOffset); 155 | rOffsetTextfield.setStringValue(''+rOffset); 156 | 157 | return [alert]; 158 | } 159 | 160 | function showAvailableUpdateDialog() { 161 | var window = createDownloadWindow(); 162 | var alert = window[0]; 163 | // When “Ok” is clicked 164 | var response = alert.runModal(); 165 | if (response == "1000") { 166 | //globalContext.document.showMessage("Go to download"); 167 | openUrlInBrowser("http://guides.pratikshah.website/download.php"); 168 | } else { 169 | //globalContext.document.showMessage("Check later"); 170 | setUpdateCheckDayOnTomorrow(); 171 | } 172 | } 173 | 174 | function setUpdateCheckDayOnTomorrow() { 175 | var newTime = new Date(); 176 | newTime.setDate(newTime.getDate() + 1); 177 | configData.localUpdateTime = newTime.getTime(); 178 | saveData(configData); 179 | } 180 | 181 | function openUrlInBrowser(url) { 182 | NSWorkspace.sharedWorkspace().openURL(NSURL.URLWithString(url)); 183 | trackEvent("openUrlInBrowser", url, 1); 184 | } 185 | 186 | function getRemoteJson(url) { 187 | /*var request = NSURLRequest.requestWithURL(NSURL.URLWithString(url)); 188 | var response = NSURLConnection.sendSynchronousRequest_returningResponse_error(request, null, null); 189 | var content = NSString.alloc().initWithData_encoding(response, NSUTF8StringEncoding); 190 | var result = null; 191 | if(content) { 192 | result = JSON.parse(content); 193 | }*/ 194 | var result = null; 195 | result = networkRequest([""+url]); 196 | //log("content:" + content + " |"); 197 | return result; 198 | } 199 | 200 | function tryParseJSON (jsonString){ 201 | try { 202 | var o = JSON.parse(jsonString); 203 | 204 | // Handle non-exception-throwing cases: 205 | // Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking, 206 | // but... JSON.parse(null) returns 'null', and typeof null === "object", 207 | // so we must check for that, too. 208 | if (o && typeof o === "object" && o !== null) { 209 | return o; 210 | } 211 | } 212 | catch (e) { } 213 | 214 | return false; 215 | } 216 | 217 | function networkRequest(args) { 218 | var task = NSTask.alloc().init(); 219 | task.setLaunchPath("/usr/bin/curl"); 220 | task.setArguments(args); 221 | var outputPipe = [NSPipe pipe]; 222 | [task setStandardOutput:outputPipe]; 223 | task.launch(); 224 | var responseData = [[outputPipe fileHandleForReading] readDataToEndOfFile]; 225 | var garesponse = "<47494638 39610100 010080ff 00ffffff 0000002c 00000000 01000100 00020244 01003b>"; 226 | if (responseData == garesponse) { 227 | //log("it works"); 228 | return true; 229 | } else { 230 | var responseString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]]; 231 | var parsed = tryParseJSON(responseString); 232 | if(!parsed) { 233 | /*log("Error invoking curl"); 234 | log("args:"); 235 | log(args); 236 | log("responseData"); 237 | log(responseData); 238 | log("responseString"); 239 | log(responseString);*/ 240 | throw "Error communicating with server" 241 | } 242 | return parsed; 243 | } 244 | } 245 | 246 | function trackEvent(action, label, value) { 247 | /* 248 | var kUUIDKey = 'google.analytics.uuid' 249 | var uuid = NSUserDefaults.standardUserDefaults().objectForKey(kUUIDKey) 250 | if (!uuid) { 251 | uuid = NSUUID.UUID().UUIDString() 252 | NSUserDefaults.standardUserDefaults().setObject_forKey(uuid, kUUIDKey) 253 | } 254 | 255 | var tid = "UA-64818389-6"; 256 | var cid = uuid; 257 | //cid = "e4567790-98b3-4f6d-85b3-6c5345d9ad00"; 258 | var ds = "Sketch-" + NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleShortVersionString"); 259 | // var an = globalContext.plugin.name(); 260 | // var aid = globalContext.plugin.identifier(); 261 | // var av = globalContext.plugin.version(); 262 | 263 | var baseURL = "https://www.google-analytics.com/debug/collect?v=1&ds=" + ds + "&t=event&tid=" + tid + "&cid=" + cid; 264 | baseURL = "https://www.google-analytics.com/collect?v=1&ds=" + ds + "&t=event&tid=" + tid + "&cid=" + cid; 265 | var version = configData.localVersion; 266 | 267 | var trackingURL = baseURL + "&ec=PSGuides-" + version + "&ea=" + action + "&el=" + label + "&ev=" + value; 268 | //globalContext.document.showMessage("URL: " + trackingURL); 269 | // console.log("URL: " + trackingURL); 270 | // var url = NSURL.URLWithString(NSString.stringWithFormat(trackingURL)) 271 | // if (url) { 272 | // NSURLSession.sharedSession().dataTaskWithURL(url).resume() 273 | // } 274 | getRemoteJson(trackingURL); 275 | //globalContext.document.showMessage("URL: " + trackingURL); 276 | */ 277 | 278 | 279 | var trackingID = "UA-64818389-6"; 280 | var userDefaults = NSUserDefaults.standardUserDefaults(); 281 | 282 | var uuidKey = "google.analytics.uuid"; 283 | var uuid = userDefaults.objectForKey(uuidKey); 284 | if (!uuid) { 285 | uuid = NSUUID.UUID().UUIDString(); 286 | userDefaults.setObject_forKey(uuid, uuidKey); 287 | userDefaults.synchronize(); 288 | } 289 | 290 | var appName = encodeURI(globalContext.plugin.name()), 291 | appId = globalContext.plugin.identifier(), 292 | appVersion = globalContext.plugin.version(); 293 | 294 | var url = "https://www.google-analytics.com/collect?v=1"; 295 | // Tracking ID 296 | url += "&tid=" + trackingID; 297 | // Source 298 | url += "&ds=sketch" + MSApplicationMetadata.metadata().appVersion; 299 | // Client ID 300 | url += "&cid=" + uuid; 301 | // User GEO location 302 | url += "&geoid=" + NSLocale.currentLocale().countryCode(); 303 | // User language 304 | url += "&ul=" + NSLocale.currentLocale().localeIdentifier().toLowerCase(); 305 | // pageview, screenview, event, transaction, item, social, exception, timing 306 | url += "&t=event"; 307 | // App Name 308 | url += "&an=" + appName; 309 | // App ID 310 | url += "&aid=" + appId; 311 | // App Version 312 | url += "&av=" + appVersion; 313 | // Event category 314 | url += "&ec=" + encodeURI("PSGuides-" + appVersion); 315 | // Event action 316 | // url += "&ea=" + encodeURI(eventAction); 317 | url += "&ea=" + encodeURI(action); 318 | // Event label 319 | // if (eventLabel) { 320 | // url += "&el=" + encodeURI(eventLabel); 321 | // } 322 | url += "&el=" + encodeURI(label); 323 | // Event value 324 | // if (eventValue) { 325 | // url += "&ev=" + encodeURI(eventValue); 326 | // } 327 | url += "&ev=" + encodeURI(value); 328 | 329 | // console.log("new ga url: " + url); 330 | 331 | var session = NSURLSession.sharedSession(); 332 | var task = session.dataTaskWithURL(NSURL.URLWithString(NSString.stringWithString(url))); 333 | task.resume(); 334 | 335 | } 336 | 337 | function saveData(data) { 338 | var string = [NSString stringWithFormat: "%@", JSON.stringify(data)]; 339 | [string writeToFile: localDataPath atomically: true encoding: NSUTF8StringEncoding error: nil]; 340 | } 341 | 342 | function readData() { 343 | if(NSFileManager.defaultManager().fileExistsAtPath(localDataPath)){ 344 | var string = NSString.stringWithContentsOfFile_encoding_error(localDataPath,4, nil); 345 | string = string.replace(/(\r\n|\n|\r)/gm,""); 346 | var data = JSON.parse(string); 347 | return data; 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /PS Guides.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "📐 Sketch Guides", 3 | "description": "Solution for your guides problems in Sketch :)", 4 | "author": "Pratik Shah", 5 | "email": "hello@pratikshah.website", 6 | "homepage": "http://guides.pratikshah.website/", 7 | "appcast": "https://raw.githubusercontent.com/pratikjshah/PS-Guides/master/appcast.xml", 8 | "updateURL": "https://github.com/pratikjshah/PS-Guides/archive/master.zip", 9 | "downloadURL": "https://github.com/pratikjshah/PS-Guides/archive/master.zip", 10 | "version": "3.2.1", 11 | "identifier": "website.pratikshah.guides", 12 | "icon": "PS-Guides.png", 13 | "compatibleVersion": "40", 14 | "bundleVersion": "1", 15 | "commands": [ 16 | { 17 | "name": "Init", 18 | "identifier": "commandInit", 19 | "handlers": { 20 | "actions": { 21 | "OpenDocument": "commandInit" 22 | } 23 | }, 24 | "script": "handler.js" 25 | }, 26 | { 27 | "script": "handler.js", 28 | "handler": "addGuides", 29 | "shortcut": "shift cmd \\", 30 | "name": "Add Guides", 31 | "identifier": "addGuides", 32 | "icon" : "add-guides.png" 33 | }, 34 | { 35 | "script": "handler.js", 36 | "handler": "addLastGuides", 37 | "shortcut": "shift cmd ]", 38 | "name": "Add Guides with last setting", 39 | "identifier": "addLastGuides", 40 | "icon" : "add-guides.png" 41 | }, 42 | { 43 | "script": "handler.js", 44 | "handler": "clearGuides", 45 | "shortcut": "option shift cmd \\", 46 | "name": "Clear Guides", 47 | "identifier": "clearGuides", 48 | "icon" : "clear-guides.png" 49 | }, 50 | { 51 | "script": "help.js", 52 | "handler": "collapseGroupsNArtboard", 53 | "name": "Collapse Groups & Artboards", 54 | "identifier": "collapseGroupsNArtboard" 55 | }, 56 | { 57 | "script": "help.js", 58 | "handler": "reportIssue", 59 | "shortcut": "", 60 | "name": "Report issue", 61 | "identifier": "reportIssue" 62 | }, 63 | { 64 | "script": "help.js", 65 | "handler": "checkForUpdate", 66 | "shortcut": "", 67 | "name": "Check for update", 68 | "identifier": "checkForUpdate" 69 | }, 70 | { 71 | "script": "help.js", 72 | "handler": "aboutPratikShah", 73 | "shortcut": "", 74 | "name": "About Pratik Shah", 75 | "identifier": "aboutPratikShah" 76 | } 77 | ], 78 | "menu": { 79 | "items": [ 80 | "addGuides", 81 | "addLastGuides", 82 | "clearGuides", 83 | "-", 84 | "reportIssue", 85 | "checkForUpdate", 86 | "-", 87 | "aboutPratikShah" 88 | ] 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 📐Guides: Sketch plugin 3 | 4 | Quickly generate Guides for a selected element. 5 | It is tedious to create guides for a layer using sketch's default layout setting, so this is my attempt to ease that effort. 6 | 7 | ![See 📐Guides in action!](http://guides.pratikshah.website/ps-guides-example.png?t=21) 8 | 9 | ## Install 10 | 11 | *PS-Guides supports Sketch 3.8+* ([why?] Because I haven't tested in older versions! 😛) 12 | 13 | 1. [Download the ZIP ⬇️](http://guides.pratikshah.website/download.php) 14 | 2. Unzip, open the folder and double click `PS Guides.sketchplugin` 15 | 3. 🤘Done, you are good to go! Just press `[cmd + shift + \]` 16 | 17 | ## Change the keyboard shortcuts 18 | 19 | If you’re not a fan of the keyboard shortcuts 📐-Guides ships with, you can change them in `System Preferences > Keyboard > Shortcuts`. Add a new item for Sketch.app named either `Setup Guides` or `Clear Guides` and create a new one. 20 | 21 | ![Keyboard shortcuts in the system preferences](http://guides.pratikshah.website/shortcuts.png) 22 | 23 | ## Notes 24 | 25 | - Material and/or Bootstrap column layout is the most widely used standard for designs. 26 | - This came to me because I felt that there isn't a clean solution available to do grids for layers (and I had no plans on a Weekend). 27 | 28 | ## License 29 | 30 | PS-Guides is released under the MIT [`LICENSE`](https://github.com/pratikjshah/PS-Guides/blob/master/LICENSE). 31 | 32 | ## About 33 | 34 | PS-Guides is made by [Pratik Shah](http://pratikshah.website). 35 | 36 | - Website: [http://pratikshah.website](http://pratikshah.website) 37 | - Dribbble: [https://dribbble.com/pratikshah](https://dribbble.com/pratikshah) 38 | - LinkedIn: [https://in.linkedin.com/in/shahpratikj](https://in.linkedin.com/in/shahpratikj) 39 | - Email: [hello@pratikshah.website](hello@pratikshah.website) 40 | 41 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 📐 Sketch Guides 5 | https://github.com/pratikjshah/PS-Guides/appcast.xml 6 | Solution for your guides problems in Sketch :) 7 | en 8 | 9 | 10 | v3.2.1 11 | 12 | For Sketch 62+ 14 |
    15 |
  • Added Fix for Sketch 62+
  • 16 |
17 | ]]> 18 |
19 | Sun, 08 Mar 2020 23:05:00 GMT 20 | 24 |
25 | 26 | 27 | v2.0.4 28 | 29 | For Sketch 52 31 |
  • Added support for Dark mode.
  • 32 |
  • Added TAB key navigation between fields
  • 33 |
  • Smart Guides: Suggest stretching or shrinking selection area to achieve pixel perfect grids.
  • 34 |
  • Performance improvements and bug fixing
35 | ]]> 36 |
37 | Thu, 27 Dec 2018 21:54:24 GMT 38 | 42 |
43 | 44 | 45 | v1.6.1 46 | 47 | For Sketch 45 49 |
  • Added support for Sketch 45 plugin update system.
  • 50 |
  • Added Collapse Groups and folder feature
  • 51 |
  • Performance improvements and bug fixing
52 | ]]> 53 |
54 | Wed, 05 Jul 2017 16:30:24 GMT 55 | 59 |
60 | 61 | 62 | v1.5.2 63 | 64 | For Sketch 45 66 |
  • Added support for Sketch 45 plugin update system.
  • 67 |
  • Added Collapse Groups and folder feature
68 | ]]> 69 |
70 | Fri, 09 Jun 2017 20:30:24 GMT 71 | 75 |
76 | 77 | 78 | v1.5.1 79 | 80 | For Sketch 43.x 82 | ]]> 83 | 84 | Sat, 22 Apr 2017 23:05:28 GMT 85 | 89 | 90 | 91 |
92 |
93 | --------------------------------------------------------------------------------