├── HandyTools.sketchplugin
└── Contents
│ └── Sketch
│ ├── .idea
│ ├── .name
│ ├── Sketch.iml
│ ├── encodings.xml
│ ├── misc.xml
│ ├── modules.xml
│ └── workspace.xml
│ ├── algin.js
│ ├── hidden_others.js
│ ├── libs
│ └── common.js
│ ├── manifest.json
│ ├── space.js
│ ├── unhidden_all.js
│ └── unhidden_all_artboard.js
├── README.md
└── Screenshot
├── AlignTo.gif
├── dir_info.png
├── hidden.png
└── spacing.gif
/HandyTools.sketchplugin/Contents/Sketch/.idea/.name:
--------------------------------------------------------------------------------
1 | Sketch
--------------------------------------------------------------------------------
/HandyTools.sketchplugin/Contents/Sketch/.idea/Sketch.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/HandyTools.sketchplugin/Contents/Sketch/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/HandyTools.sketchplugin/Contents/Sketch/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/HandyTools.sketchplugin/Contents/Sketch/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/HandyTools.sketchplugin/Contents/Sketch/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
81 |
82 |
83 |
84 |
85 | true
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 | 1449326249031
165 |
166 | 1449326249031
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
--------------------------------------------------------------------------------
/HandyTools.sketchplugin/Contents/Sketch/algin.js:
--------------------------------------------------------------------------------
1 | @import 'libs/common.js'
2 |
3 | var lastDir = getConfig('last_algin_dir');
4 | lastDir = lastDir == null ? 1 : parseInt(lastDir);
5 |
6 | function disableCell(cell)
7 | {
8 | [cell setEnabled:false]
9 | [cell setTransparent:true]
10 | }
11 |
12 | function qq(cellArr,idxArr,call)
13 | {
14 | for (var i= 0;i 0){
12 | processAllLayers(g_layers,function(layer){
13 | [layer setIsVisible:false];
14 | });
15 | }
16 |
17 | function processAllLayers(layers,callback) {
18 | for (var i = 0; i < [layers count]; i++) {
19 | var layer = [layers objectAtIndex:i];
20 | var isSkip = [selection indexOfObject:layer] < MAX_COUNT
21 | if (isGroup(layer)) {
22 | if (isSkip) continue;
23 | processAllLayers([layer layers], callback);
24 | }else{
25 | if (!isSkip) callback(layer);
26 | }
27 | }
28 | }
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/HandyTools.sketchplugin/Contents/Sketch/libs/common.js:
--------------------------------------------------------------------------------
1 | // const page = [doc currentPage],
2 | // artboard = [page currentArtboard],
3 | // artboards = [doc artboards],
4 | // selection = context.selection,
5 | // doc = context.document,
6 | // current = artboard ? artboard : page,
7 | const prefix = 'kiny',
8 | measureRegEx = /\$SIZE|\$WIDTH|\$HEIGHT|\$DISTANCE|\$PROPERTY|\$LABEL|\$OVERLAYER|\$COORDINATE/;
9 |
10 |
11 | function absRect2CGRect(rect)
12 | {
13 | return CGRectMake(rect.x(),rect.y(),rect.width(),rect.height())
14 | }
15 |
16 | function isMeasure(layer)
17 | {
18 | return measureRegEx.exec([layer name]);
19 | }
20 |
21 | function is(layer, theClass){
22 | var cls = [layer class];
23 | return cls === theClass;
24 | }
25 |
26 | function isPage(layer){
27 | return is(layer, MSPage);
28 | }
29 |
30 | function isGroup(layer){
31 | return is(layer, MSLayerGroup);
32 | }
33 |
34 | function isText(layer){
35 | return is(layer, MSTextLayer);
36 | }
37 |
38 | function isShape(layer){
39 | return is(layer, MSShapeGroup);
40 | }
41 |
42 | function getConfig(key) {
43 | var defaults = [NSUserDefaults standardUserDefaults];
44 | return [defaults objectForKey: '-' + prefix + '-' + key];
45 | }
46 |
47 | function setConfig(key, value) {
48 | var defaults = [NSUserDefaults standardUserDefaults],
49 | configs = [NSMutableDictionary dictionary];
50 | [configs setObject: value forKey: '-' + prefix + '-' + key]
51 | return [defaults registerDefaults: configs];
52 | }
53 |
54 | function toJSArray(arr) {
55 | var len = [arr count],
56 | res = [];
57 | while(len--){
58 | res.push(arr[len]);
59 | }
60 | return res;
61 | }
62 |
63 | function createLabel(text, frame ,editable) {
64 | editable = editable || false;
65 | var label = [[NSTextField alloc] initWithFrame:frame];
66 | [label setStringValue:text];
67 | [label setFont:[NSFont boldSystemFontOfSize:12]];
68 | [label setBezeled:false];
69 | [label setDrawsBackground:editable];
70 | [label setEditable:editable];
71 | [label setSelectable:editable];
72 | return label;
73 | }
--------------------------------------------------------------------------------
/HandyTools.sketchplugin/Contents/Sketch/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Handy Tools",
3 | "description": "Some handy tools for Sketch",
4 | "author": "webpatch",
5 | "homepage": "https://github.com/webpatch/Handy-Tools",
6 | "version": 1.1,
7 | "identifier": "com.hikiny.sketch.handytools",
8 | "compatibleVersion": 3,
9 | "commands": [
10 | {
11 | "name": "Algin to...",
12 | "identifier": "algin",
13 | "shortcut": "ctrl shift a",
14 | "script": "algin.js",
15 | "handler": "onRun"
16 | },
17 | {
18 | "name": "Space...",
19 | "identifier": "space",
20 | "shortcut": "ctrl shift s",
21 | "script": "space.js",
22 | "handler": "onRun"
23 | },
24 | {
25 | "name": "Hidden others",
26 | "identifier": "hideOthers",
27 | "shortcut": "",
28 | "script": "hidden_others.js",
29 | "handler": "onRun"
30 | },
31 | {
32 | "name": "Unhidden all",
33 | "identifier": "unhiddenAll",
34 | "shortcut": "",
35 | "script": "unhidden_all.js",
36 | "handler": "onRun"
37 | },
38 | {
39 | "name": "Unhidden all Artboards",
40 | "identifier": "unhiddenAllArtboards",
41 | "shortcut": "",
42 | "script": "unhidden_all_artboard.js",
43 | "handler": "onRun"
44 | }
45 | ],
46 | "menu": {
47 | "items": [
48 | "algin",
49 | "space",
50 | {
51 | "title": "Hidden & Unhidden",
52 | "items": [
53 | "hideOthers",
54 | "unhiddenAll",
55 | "unhiddenAllArtboards"
56 | ]
57 | }
58 | ],
59 | "title": "Handy Tools"
60 | }
61 | }
--------------------------------------------------------------------------------
/HandyTools.sketchplugin/Contents/Sketch/space.js:
--------------------------------------------------------------------------------
1 | @import 'libs/common.js'
2 |
3 | var lastDir = getConfig('last_spacing_dir');
4 | lastDir = lastDir == null ? 1 : parseInt(lastDir)
5 |
6 | function createAlert(msg)
7 | {
8 | var viewBox = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 320, 40)];
9 | [viewBox addSubview:createLabel("Direction:",NSMakeRect(0, 1, 70, 25),false)];
10 |
11 | var prototype = [[NSButtonCell alloc] init];
12 | [prototype setBezelStyle: NSRegularSquareBezelStyle];
13 | [prototype setButtonType:NSPushOnPushOffButton];
14 | var myMatrix = [[NSMatrix alloc] initWithFrame:NSMakeRect(65, 0, 260, 35)
15 | mode:NSRadioModeMatrix
16 | prototype:prototype
17 | numberOfRows:1
18 | numberOfColumns:2];
19 | var cellArray = [myMatrix cells];
20 | [myMatrix setCellSize:CGSizeMake(40,35)];
21 | [myMatrix selectCellAtRow:0 column:lastDir];
22 | [[cellArray objectAtIndex:0] setTitle:"⬌"];
23 | [[cellArray objectAtIndex:1] setTitle:"⬍"];
24 | [viewBox addSubview:myMatrix];
25 |
26 | [viewBox addSubview:createLabel("Spacing:",NSMakeRect(160, 1, 60, 25),false)];
27 |
28 | var input = createLabel("10",NSMakeRect(225, 6, 90, 22),true);
29 | [input setFont:[NSFont systemFontOfSize:17]];
30 | [viewBox addSubview:input];
31 |
32 | var alert = [[NSAlert alloc] init];
33 | [alert setMessageText:msg];
34 | [alert addButtonWithTitle:"OK"];
35 | [alert addButtonWithTitle:"Cancel"];
36 | [alert setAccessoryView:viewBox];
37 |
38 | var responseCode = [alert runModal];
39 | var sel = [input integerValue];
40 | var dir = [[myMatrix selectedCell] title].trim();
41 | return [responseCode, sel,dir];
42 | }
43 |
44 | function sort_by_position_left(a,b){
45 | return [[a absoluteRect] x] - [[b absoluteRect] x];
46 | }
47 |
48 | function sort_by_position_top(a,b){
49 | return [[a absoluteRect] y] - [[b absoluteRect] y];
50 | }
51 |
52 | function horizontalSpacing(spacing,selection)
53 | {
54 | var sorted_selection = toJSArray(selection).sort(sort_by_position_left),
55 | first_element = sorted_selection[0],
56 | left_position = [[first_element absoluteRect] x];
57 |
58 | sorted_selection.forEach(function(layer){
59 | var parentRect = [[layer parentGroup] absoluteRect];
60 | [[layer frame] setX:left_position-parentRect.x()];
61 | var f = [layer absoluteRect]
62 | left_position = f.x() + f.width() + spacing;
63 | });
64 | }
65 |
66 | function verticallSpacing(spacing,selection)
67 | {
68 | var sorted_selection = toJSArray(selection).sort(sort_by_position_top),
69 | first_element = sorted_selection[0],
70 | top_position = [[first_element absoluteRect] y];
71 |
72 | sorted_selection.forEach(function(layer){
73 | var parentRect = [[layer parentGroup] absoluteRect];
74 | [[layer frame] setY:top_position-parentRect.y()];
75 | var f = [layer absoluteRect]
76 | top_position = f.y() + f.height() + spacing;
77 | });
78 | }
79 |
80 | var onRun = function(context) {
81 | var doc = context.document;
82 | var selection = context.selection;
83 |
84 | if([selection count] < 2){
85 | [doc showMessage:"Please select 2 or more layers."];
86 | }else {
87 | var choice = createAlert("Layers Spacing");
88 | if (choice[0] == 1000)
89 | {
90 | var spacing = choice[1];
91 | switch(choice[2]){
92 | case "⬌":
93 | horizontalSpacing(spacing,selection);
94 | setConfig('last_spacing_dir',0);
95 | break;
96 | case "⬍":
97 | verticallSpacing(spacing,selection);
98 | setConfig('last_spacing_dir',1);
99 | break;
100 | }
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/HandyTools.sketchplugin/Contents/Sketch/unhidden_all.js:
--------------------------------------------------------------------------------
1 | @import 'libs/common.js'
2 |
3 | var onRun = function(context){
4 | const selection = context.selection,
5 | doc = context.document,
6 | page = [doc currentPage],
7 | artboard = [page currentArtboard],
8 | layers = [artboard layers];
9 |
10 | function processAllLayers(layers, callback) {
11 | for (var i = 0; i < [layers count]; i++) {
12 | var layer = [layers objectAtIndex:i];
13 | if (isMeasure(layer)) continue;
14 | if ([layer isMemberOfClass:[MSLayerGroup class]]) {
15 | callback(layer);
16 | processAllLayers([layer layers], callback);
17 | }
18 | else {
19 | callback(layer);
20 | }
21 | }
22 | }
23 |
24 | processAllLayers(layers,function(layer){
25 | if(![layer isVisible])[layer setIsVisible:true];
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/HandyTools.sketchplugin/Contents/Sketch/unhidden_all_artboard.js:
--------------------------------------------------------------------------------
1 | @import 'libs/common.js'
2 |
3 | var onRun = function(context){
4 | const doc = context.document,
5 | artboards = [doc artboards];
6 |
7 | function processAllLayers(layers, callback) {
8 | for (var i = 0; i < [layers count]; i++) {
9 | var layer = [layers objectAtIndex:i];
10 | if (isMeasure(layer)) continue;
11 | if ([layer isMemberOfClass:[MSLayerGroup class]]) {
12 | callback(layer);
13 | processAllLayers([layer layers], callback);
14 | }else {
15 | callback(layer);
16 | }
17 | }
18 | }
19 |
20 | for (var j = 0; j < [artboards count]; j++){
21 | var artboard = [artboards objectAtIndex:j];
22 | var layers = [artboard layers];
23 | processAllLayers(layers,function(layer){
24 | if(![layer isVisible]) [layer setIsVisible:true];
25 | })
26 | }
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Handy-Tools
2 | Some handy tools for Sketch
3 |
4 | ## Require
5 | Sketch 3.3+
6 |
7 | ## Changelog
8 | ### v1.1
9 | * Solved multi-level layer's align and space problem
10 |
11 | ## Installing Plugin
12 |
13 | 1. [Download Plugin](https://github.com/webpatch/Handy-Tools/releases/download/v1.1/HandyTools.sketchplugin.zip)
14 | 2. Extract the zip file, dobule click the "HandyTools.sketchplugin", it will install automatic.
15 | 3. You can find these tools in Sketch's "plugins" menu
16 |
17 | ## Feature
18 | ### Align To Key Object
19 | 
20 |
21 | Align Direction
22 |
23 | 
24 |
25 | ### Equal Spacing
26 | 
27 |
28 | ### Hidden/Unhidden
29 |
30 | 
31 |
32 | * Hidden other layers (exclude selected layers)
33 | * Unhidden all layers
34 | * Unhidden all artboard's layers
35 |
--------------------------------------------------------------------------------
/Screenshot/AlignTo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpatch/Handy-Tools/cf08b8de9ec81d1cc75f5ae4d30deb2e7b3912fe/Screenshot/AlignTo.gif
--------------------------------------------------------------------------------
/Screenshot/dir_info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpatch/Handy-Tools/cf08b8de9ec81d1cc75f5ae4d30deb2e7b3912fe/Screenshot/dir_info.png
--------------------------------------------------------------------------------
/Screenshot/hidden.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpatch/Handy-Tools/cf08b8de9ec81d1cc75f5ae4d30deb2e7b3912fe/Screenshot/hidden.png
--------------------------------------------------------------------------------
/Screenshot/spacing.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpatch/Handy-Tools/cf08b8de9ec81d1cc75f5ae4d30deb2e7b3912fe/Screenshot/spacing.gif
--------------------------------------------------------------------------------