├── .DS_Store ├── README.md ├── icon.png ├── screenshot.png └── symbolState.sketchplugin └── Contents ├── Resources ├── icon-sr.png └── icon.png └── Sketch ├── manifest.json └── symbolState.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedmigo/Symbol-state/43cc3a103a3169595710619a90b5122d152485ef/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Symbol State Sketchplugin Plugin 2 | 3 | 4 | 5 | 6 | 7 | This plugin will make you save your overrides in any symbol in what I called "STATE", then you can re-use this overrides on any instance of this symbol 8 | let's say we have button with nested symbols and you can change the background and the text by overrides. what if you saved each override with a name and then reuse them everywhere. 9 | give me your thoughts and if it will be useful or not 10 | 11 | 12 |

How it works

13 |

14 | first go to any symbol instance and modify the overrides as you wish 15 | then go to Symbol state plugin and select Add Symbol State, After that write the name you want for this state, then create any instance from this symbole and select from Symbole state plugin "Set Symbole State" you will find the state you saved in the this symbole in the drop down menu select it then your saved overrides will be applied to the selected instance 16 |

17 | 18 | 19 | then create any instance from this symbol and select from Symbol state plugin "Set Symbol State" you will find the state you saved in the this symbol in the drop down menu select it then your saved overrides will be applied to the selected instance 20 |

21 | 22 | 23 | Available on 24 | 25 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedmigo/Symbol-state/43cc3a103a3169595710619a90b5122d152485ef/icon.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedmigo/Symbol-state/43cc3a103a3169595710619a90b5122d152485ef/screenshot.png -------------------------------------------------------------------------------- /symbolState.sketchplugin/Contents/Resources/icon-sr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedmigo/Symbol-state/43cc3a103a3169595710619a90b5122d152485ef/symbolState.sketchplugin/Contents/Resources/icon-sr.png -------------------------------------------------------------------------------- /symbolState.sketchplugin/Contents/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedmigo/Symbol-state/43cc3a103a3169595710619a90b5122d152485ef/symbolState.sketchplugin/Contents/Resources/icon.png -------------------------------------------------------------------------------- /symbolState.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Symbol State", 3 | "identifier" : "symbol.state.plugin", 4 | "version" : "1.1", 5 | "description" : "Make states from symbol instance", 6 | "authorEmail" : "ahmad.migo@gmail.com", 7 | "URL": "https://github.com/ahmedmigo/Symbol-state", 8 | "author" : "Ahmed Genaidy", 9 | 10 | "commands" : [ 11 | { 12 | "script" : "symbolState.js", 13 | "handler" : "setStatus", 14 | "shortcut" : "cmd alt a", 15 | "name" : "Add Symbol State", 16 | "description" : "Add New State for the selected Symbol", 17 | "identifier" : "set.State", 18 | "icon" : "icon-sr.png" 19 | }, 20 | { 21 | "script" : "symbolState.js", 22 | "handler" : "deleteSymbolStates", 23 | "shortcut" : "cmd alt r", 24 | "name" : "Delete Symbol State", 25 | "description" : "Delete existing State from the selected Symbol", 26 | "identifier" : "delete.State", 27 | "icon" : "icon-sr.png" 28 | }, 29 | { 30 | "script" : "symbolState.js", 31 | "handler" : "getSymbolStates", 32 | "shortcut" : "cmd e", 33 | "name" : "Set Symbol State", 34 | "description" : "Select State from the Symbol states and apply it to instance", 35 | "identifier" : "get.State", 36 | "icon" : "icon-sr.png" 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /symbolState.sketchplugin/Contents/Sketch/symbolState.js: -------------------------------------------------------------------------------- 1 | function createSelect(items,selectedItemIndex,frame) { 2 | selectedItemIndex = (selectedItemIndex > -1) ? selectedItemIndex : 0; 3 | var comboBox = [[NSComboBox alloc] initWithFrame:frame]; 4 | [comboBox addItemsWithObjectValues:items]; 5 | [comboBox selectItemAtIndex:selectedItemIndex]; 6 | 7 | return comboBox; 8 | } 9 | 10 | // create text field 11 | function createField(value,frame) { 12 | var field = [[NSTextField alloc] initWithFrame:frame]; 13 | [field setStringValue:value]; 14 | 15 | return field; 16 | } 17 | 18 | 19 | function createSymboleInstancePreview (instance, StateString) { 20 | 21 | var exportRequest = MSExportRequest.exportRequestsFromExportableLayer_inRect_useIDForName_( 22 | instance, 23 | instance.absoluteInfluenceRect(), 24 | false 25 | ).firstObject() 26 | 27 | exportRequest.format = "png" 28 | 29 | var scaleX = 300 / exportRequest.rect().size.width; 30 | var scaleY = (100-20) / exportRequest.rect().size.height; 31 | 32 | if(scaleX 400) 49 | scrollHeight = 400; 50 | 51 | var listContainer = [[NSScrollView alloc] initWithFrame:NSMakeRect(0,0,cellWidth,scrollHeight)]; 52 | 53 | listContainer.setDocumentView(matrix); 54 | 55 | if(scrollHeight != matrixHeight) 56 | listContainer.setHasVerticalScroller(true); 57 | return listContainer 58 | } 59 | 60 | // create Label header 61 | function createLabel(text,size,frame) { 62 | var label = [[NSTextField alloc] initWithFrame:frame]; 63 | [label setStringValue:text]; 64 | [label setFont:[NSFont boldSystemFontOfSize:size]]; 65 | [label setBezeled:false]; 66 | [label setDrawsBackground:false]; 67 | [label setEditable:false]; 68 | [label setSelectable:false]; 69 | 70 | return label; 71 | } 72 | 73 | 74 | // create text description 75 | function createDescription(text,size,frame) { 76 | var label = [[NSTextField alloc] initWithFrame:frame]; 77 | [label setStringValue:text]; 78 | [label setFont:[NSFont systemFontOfSize:size]]; 79 | [label setTextColor:[NSColor colorWithCalibratedRed:(0/255) green:(0/255) blue:(0/255) alpha:0.6]]; 80 | [label setBezeled:false]; 81 | [label setDrawsBackground:false]; 82 | [label setEditable:false]; 83 | [label setSelectable:false]; 84 | 85 | return label; 86 | } 87 | 88 | 89 | function setKeyOrder(alert,order) { 90 | for (var i = 0; i < order.length; i++) { 91 | var thisItem = order[i]; 92 | var nextItem = order[i+1]; 93 | 94 | if (nextItem) thisItem.setNextKeyView(nextItem); 95 | } 96 | 97 | alert.alert().window().setInitialFirstResponder(order[0]); 98 | } 99 | 100 | 101 | function getLayoutSettings(context,type,stateArray) { 102 | // Document variables 103 | var page = context.document.currentPage(); 104 | // Setting variables 105 | var defaultSettings = {}; 106 | defaultSettings.groupDepth = 1; 107 | defaultSettings.displayTitles = 0; 108 | defaultSettings.sortDirection = 0; 109 | defaultSettings.xPad = '100'; 110 | defaultSettings.yPad = '100'; 111 | defaultSettings.maxPer = ''; 112 | defaultSettings.reverseOrder = 0; 113 | defaultSettings.renameSymbols = 0; 114 | defaultSettings.gatherSymbols = 0; 115 | defaultSettings.removeSymbols = 0; 116 | 117 | 118 | // If type is set and equal to "config", operate in config mode... 119 | // Establish the alert window 120 | var alertWindow = COSAlertWindow.new(); 121 | //alertWindow.setIcon(NSImage.alloc().initByReferencingFile(context.plugin.urlForResourceNamed("icon.png").path())); 122 | alertWindow.setMessageText("Symbol State Plugin"); 123 | 124 | // Grouping options 125 | var groupFrame = NSView.alloc().initWithFrame(NSMakeRect(0,0,300,124)); 126 | alertWindow.addAccessoryView(groupFrame); 127 | 128 | 129 | 130 | if (type == "getStates"){ 131 | var groupGranularityLabel = createLabel('Select Symbol State',12,NSMakeRect(0,108,140,16)); 132 | groupFrame.addSubview(groupGranularityLabel); 133 | 134 | var groupGranularityDescription = createDescription('Select the overrides state that you need for this instance',11,NSMakeRect(0,60,300,42)); 135 | groupFrame.addSubview(groupGranularityDescription); 136 | var groupGranularityValue = createSelect(stateArray,0,NSMakeRect(0,40,300,28)); 137 | groupFrame.addSubview(groupGranularityValue); 138 | // Buttons 139 | alertWindow.addButtonWithTitle('GO 🎉 '); 140 | alertWindow.addButtonWithTitle('Cancel'); 141 | } else if (type == "deleteState"){ 142 | var groupGranularityLabel = createLabel('Delete Symbol State',12,NSMakeRect(0,108,140,16)); 143 | groupFrame.addSubview(groupGranularityLabel); 144 | var groupGranularityDescription = createDescription('Select the state that you want to remove from this symbol',11,NSMakeRect(0,60,300,42)); 145 | groupFrame.addSubview(groupGranularityDescription); 146 | var groupGranularityValue = createSelect(stateArray,0,NSMakeRect(0,40,300,28)); 147 | groupFrame.addSubview(groupGranularityValue); 148 | alertWindow.addButtonWithTitle('Remove ❌'); 149 | alertWindow.addButtonWithTitle('Cancel'); 150 | } 151 | else if (type == "setState"){ 152 | var groupGranularityLabel = createLabel('Add Symbol State',12,NSMakeRect(0,108,140,16)); 153 | groupFrame.addSubview(groupGranularityLabel); 154 | var groupGranularityDescription = createDescription('Write down the state name you want to save',11,NSMakeRect(0,62,300,42)); 155 | groupFrame.addSubview(groupGranularityDescription); 156 | var layoutMaxValue = createField("",NSMakeRect(0,65,250,22)); 157 | groupFrame.addSubview(layoutMaxValue); 158 | // Buttons 159 | alertWindow.addButtonWithTitle('Add'); 160 | alertWindow.addButtonWithTitle('Cancel'); 161 | 162 | } 163 | 164 | // Set key order and first responder 165 | setKeyOrder(alertWindow,[ 166 | groupGranularityValue 167 | 168 | ]); 169 | 170 | var responseCode = alertWindow.runModal(); 171 | log(responseCode) 172 | if (responseCode == 1000) { 173 | if (type == "getStates"){ 174 | log(stateArray[[groupGranularityValue indexOfSelectedItem]]) 175 | setStateToSymbol(context,stateArray[[groupGranularityValue indexOfSelectedItem]]) 176 | context.document.showMessage("Your Symbol instance updated to [" + stateArray[[groupGranularityValue indexOfSelectedItem]] + "] Successfully 😎"); 177 | } 178 | else if(type == "deleteState"){ 179 | log (stateArray[[groupGranularityValue indexOfSelectedItem]]) 180 | deleteStatefromSymbol(context,stateArray[[groupGranularityValue indexOfSelectedItem]]) 181 | context.document.showMessage("[" + stateArray[[groupGranularityValue indexOfSelectedItem]] + "] state has been deleted Successfully ❌"); 182 | } 183 | else if (type == "setState"){ 184 | var overrides = getCurrentInstance(context); 185 | addStateToMasterSymbolDocumentData(context,overrides,[layoutMaxValue stringValue]) 186 | log([layoutMaxValue stringValue]) 187 | context.document.showMessage("[" +[layoutMaxValue stringValue] + "] state has been Added Successfully ✅ 😎") 188 | } 189 | } 190 | } 191 | 192 | function getInstanceOverrides (instance) { 193 | var NewObj = new Object(); 194 | var parentLayers = instance.symbolMaster().children() 195 | var instantObjectId = instance.objectID().toString(); 196 | for (var i = 0 ; i< parentLayers.count();i++) 197 | { 198 | if (parentLayers[i].class() == MSSymbolInstance) 199 | { 200 | var parentLayersObjectId = parentLayers[i].objectID().toString(); 201 | var tempNewObj = getInstanceOverrides(parentLayers[i]) 202 | if (!isEmpty(tempNewObj)) 203 | { 204 | NewObj[parentLayersObjectId] = tempNewObj; 205 | } 206 | 207 | if (parentLayers[i].overrides()) { 208 | var _tempParentLayers = new Object(); 209 | for (var keys in parentLayers[i].overrides()) 210 | { 211 | if(!(!isEmpty(parentLayers[i].overrides()[keys]) && parentLayers[i].overrides()[keys].class() == __NSDictionary0)) 212 | { 213 | _tempParentLayers[keys] = parentLayers[i].overrides()[keys] 214 | 215 | } 216 | } 217 | 218 | if ( NewObj[parentLayersObjectId] == undefined) 219 | { 220 | NewObj[parentLayersObjectId] = _tempParentLayers 221 | } 222 | else { 223 | NewObj[parentLayersObjectId] = merge_options(NewObj[parentLayersObjectId],_tempParentLayers) 224 | } 225 | } 226 | } 227 | } 228 | return NewObj; 229 | } 230 | 231 | 232 | function setStateToSymbol(context,name) 233 | { 234 | var Parentselectedlayer = context.selection[0].symbolMaster() 235 | var selection = context.selection[0]; 236 | var overridesStates = context.command.valueForKey_onLayer('state',Parentselectedlayer); 237 | log(name) 238 | selection.overrides = overridesStates[name] 239 | } 240 | 241 | function deleteStatefromSymbol (context,name) 242 | { 243 | var Parentselectedlayer = context.selection[0].symbolMaster() 244 | var command = context.command 245 | var overridesStates = JSON.parse(JSON.stringify(command.valueForKey_onLayer('state',Parentselectedlayer))); 246 | log(overridesStates) 247 | delete overridesStates[name] 248 | log(overridesStates) 249 | command.setValue_forKey_onLayer(overridesStates,'state',Parentselectedlayer); 250 | } 251 | 252 | function addStateToMasterSymbolDocumentData(context,override,name) 253 | { 254 | var docData = context.document.documentData(); 255 | var command = context.command; 256 | var Parentselectedlayer = context.selection[0].symbolMaster() 257 | var stateInMaster = command.valueForKey_onLayer('state',Parentselectedlayer) 258 | if (stateInMaster == null) { 259 | var state = {} 260 | state[name] = override; 261 | command.setValue_forKey_onLayer(state,'state',Parentselectedlayer); 262 | } 263 | else { 264 | var state = {}; 265 | state[name] = override; 266 | state = merge_options (state,stateInMaster) 267 | command.setValue_forKey_onLayer(state,'state',Parentselectedlayer); 268 | //log (command.valueForKey_onLayer('state',Parentselectedlayer)) 269 | } 270 | 271 | } 272 | 273 | function getCurrentInstance(context) { 274 | var doc = context.document; 275 | if (context.selection.count() == 0) 276 | { 277 | doc.showMessage("No layer selected, Please select symbol instance layer"); 278 | } 279 | else if (context.selection.count() == 1 && context.selection[0].class() == MSSymbolInstance ) { 280 | var selection = context.selection[0]; 281 | var newOverrides = merge_options (getInstanceOverrides(context.selection[0]),selection.overrides()) 282 | selection.overrides = newOverrides; 283 | return newOverrides; 284 | //AddStateToMasterSymbolDocumentData(newOverrides) 285 | //doc.showMessage("you symbol State Added 😎 State:[" + context.selection[0].name() + "]" ); 286 | } 287 | else if (context.selection.count() < 1){ 288 | doc.showMessage("You are selecting multiple layers, Please select only 1 symbol instance layer that has overrides"); 289 | } 290 | else { 291 | doc.showMessage("layer selected doesn't have overrides, Please select only 1 symbol instance layer that has overrides"); 292 | } 293 | 294 | } 295 | 296 | function validate (context){ 297 | var doc = context.document; 298 | if (context.selection.count() == 0) 299 | { 300 | doc.showMessage("No layer selected, Please select symbol instance layer 🤔"); 301 | } 302 | else if (context.selection.count() == 1 && context.selection[0].class() == MSSymbolInstance ) { 303 | return true; 304 | } 305 | else if (context.selection.count() < 1){ 306 | doc.showMessage("You are selecting multiple layers, Please select only 1 symbol instance layer that has overrides 🤔"); 307 | } 308 | else { 309 | doc.showMessage("layer selected doesn't have overrides, Please select only 1 symbol instance layer that has overrides 🤔"); 310 | } 311 | return false; 312 | } 313 | 314 | function merge_options(obj1,obj2){ 315 | var obj3 = {}; 316 | for (var attrname in obj1) { 317 | obj3[attrname] = obj1[attrname]; 318 | } 319 | for (var attrname in obj2) { 320 | if ( obj3[attrname] == undefined || Object.keys(obj3[attrname]).length === 0) 321 | { 322 | obj3[attrname] = obj2[attrname]; 323 | } 324 | else 325 | { 326 | obj3[attrname] = merge_options(obj3[attrname],obj2[attrname]) 327 | } 328 | } 329 | return obj3; 330 | } 331 | 332 | 333 | function isEmpty(obj){ 334 | if (Object.keys(obj).length === 0 && obj.constructor === Object) { 335 | return true 336 | } else { 337 | return false 338 | } 339 | } 340 | 341 | function getSymbolStates(context) { 342 | if (validate (context)) { 343 | var Parentselectedlayer = context.selection[0].symbolMaster() 344 | var states = context.command.valueForKey_onLayer('state',Parentselectedlayer); 345 | if (states){ 346 | var stateValues = Object.keys(states) 347 | getLayoutSettings(context,"getStates",stateValues) 348 | } else { 349 | context.document.showMessage("there is no states saved in this symbol instance" ); 350 | } 351 | } 352 | 353 | } 354 | 355 | function deleteSymbolStates(context) { 356 | if (validate (context)) { 357 | var Parentselectedlayer = context.selection[0].symbolMaster() 358 | var states = context.command.valueForKey_onLayer('state',Parentselectedlayer); 359 | if (states && Object.keys(states).length !== 0){ 360 | var stateValues = Object.keys(states) 361 | getLayoutSettings(context,"deleteState",stateValues) 362 | } else { 363 | context.document.showMessage("there is no states saved in this symbol instance" ); 364 | } 365 | } 366 | } 367 | 368 | 369 | function setStatus(context) 370 | { 371 | if (validate (context)) { 372 | getLayoutSettings(context,"setState") 373 | } 374 | } --------------------------------------------------------------------------------