├── .gitignore
├── .sketchpacks.json
├── LICENSE
├── README.md
├── Sketch Aspect Ratio.sketchplugin
└── Contents
│ ├── Resources
│ ├── icon.png
│ ├── ratios copy.js
│ └── ratios.js
│ └── Sketch
│ ├── manifest.json
│ └── plugin.js
├── gulpfile.js
├── images
├── icon.sketch
├── set-aspect-ratio-dialog.png
├── sketch-aspect-ratio.png
└── sketch-aspect-ratio.sketch
├── package.json
└── src
├── commands
├── index.js
├── setAspectRatioList.js
├── setAspectRatioManual.js
└── setAspectRatioRepeat.js
├── plugin.js
├── resources
├── icon.png
└── ratios.js
└── utils
├── options.js
├── setAspectRatioForSelection.js
├── sketch-deprecated.js
├── sketch-dom.js
└── sketch-ui.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 | /build/
4 | /src/custom-ratios/
5 |
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/.sketchpacks.json:
--------------------------------------------------------------------------------
1 | {
2 | "schema_version": "1.0.0",
3 | "manifest_path": "Sketch%20Aspect%20Ratio.sketchplugin/Contents/Sketch/manifest.json"
4 | }
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 precious Gesellschaft für Design mbH
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | This plugin for [Sketch](https://www.sketchapp.com) helps you change the size of a layer to fit to a desired aspect ratio. Aspect ratios can be selected from a list of presets or entered manually.
5 |
6 |
7 |
8 | If you find any bugs or have suggestions for the further development please open a new [issue](https://github.com/preciousforever/sketch-aspect-ratio/issues)!
9 |
10 | ## How to install
11 |
12 | Download the latest version from the [releases page](https://github.com/preciousforever/sketch-aspect-ratio/releases), unzip it and open the file to install it in Sketch.
13 |
14 | ## How to change the preset aspect ratios
15 |
16 | The list of presets is located in [/src/resources/ratios.js](/src/resources/ratios.js). If you want to customize you can change it to your liking and then build a new version of the plugin by running `gulp build`. A new version of the plugin is then created in `/dist`. You can install this version to have your own list of presets available in Sketch.
17 |
18 | ## Development
19 |
20 | This plugin is based on Sketch Fusion by [Andrey Shakhmin, @turbobabr](https://github.com/turbobabr), as presented at [#SketcHHackday 2016](http://designtoolshackday.com).
21 |
22 | Development
23 |
24 | ```
25 | npm install
26 | gulp watch
27 | ```
28 |
29 | To release a new version as github release, run:
30 |
31 | ```
32 | export GITHUB_TOKEN=yoursecrettoken
33 | gulp release
34 | ```
35 |
--------------------------------------------------------------------------------
/Sketch Aspect Ratio.sketchplugin/Contents/Resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dataliterate/sketch-aspect-ratio/2ff4e0b4dc64d7b371c328348cdc17459bf43a33/Sketch Aspect Ratio.sketchplugin/Contents/Resources/icon.png
--------------------------------------------------------------------------------
/Sketch Aspect Ratio.sketchplugin/Contents/Resources/ratios copy.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ratios: [
3 | { x: 1, y: 1 },
4 | { x: 16, y: 10 },
5 | { x: 16, y: 9 },
6 | { x: 3, y: 1 },
7 | { x: 3, y: 2 },
8 | { x: 4, y: 3 }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/Sketch Aspect Ratio.sketchplugin/Contents/Resources/ratios.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ratios: [
3 | { x: 1, y: 1 },
4 | { x: 1, y: 3 },
5 | { x: 10, y: 16 },
6 | { x: 16, y: 10 },
7 | { x: 16, y: 9 },
8 | { x: 2, y: 3 },
9 | { x: 21, y: 9 },
10 | { x: 3, y: 1 },
11 | { x: 3, y: 2 },
12 | { x: 3, y: 4 },
13 | { x: 4, y: 3 },
14 | { x: 4, y: 5 },
15 | { x: 5, y: 3 },
16 | { x: 5, y: 4 },
17 | { x: 9, y: 16 },
18 | { x: 9, y: 2 }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/Sketch Aspect Ratio.sketchplugin/Contents/Sketch/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Aspect Ratio",
3 | "bundleName": "Sketch Aspect Ratio",
4 | "description": "Set preset or custom aspect ratios for a selected layer.",
5 | "author": "CL/precious design studio",
6 | "authorEmail": "christoph.labacher@precious-forever.com",
7 | "version": "1.0.1",
8 | "identifier": "com.precious-forever.sketch-aspect-ratio",
9 | "menu": {
10 | "isRoot": false,
11 | "items": [
12 | "setAspectRatioList",
13 | "setAspectRatioManual",
14 | "setAspectRatioRepeat"
15 | ]
16 | },
17 | "commands": [
18 | {
19 | "identifier": "setAspectRatioRepeat",
20 | "handler": "___setAspectRatioRepeat_run_handler_",
21 | "script": "plugin.js",
22 | "name": "Repeat with last options",
23 | "shortcut": ""
24 | },
25 | {
26 | "identifier": "setAspectRatioManual",
27 | "handler": "___setAspectRatioManual_run_handler_",
28 | "script": "plugin.js",
29 | "name": "Set Aspect Ratio manually",
30 | "shortcut": ""
31 | },
32 | {
33 | "identifier": "setAspectRatioList",
34 | "handler": "___setAspectRatioList_run_handler_",
35 | "script": "plugin.js",
36 | "name": "Set Aspect Ratio",
37 | "shortcut": ""
38 | }
39 | ],
40 | "disableCocoaScriptPreprocessor": true
41 | }
42 |
--------------------------------------------------------------------------------
/Sketch Aspect Ratio.sketchplugin/Contents/Sketch/plugin.js:
--------------------------------------------------------------------------------
1 | var __globals = this;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o {
37 |
38 | //save into user defaults
39 | if(newOptions.hasOwnProperty(key)) {
40 | NSUserDefaults.standardUserDefaults().setObject_forKey(newOptions[key], 'SketchAspectRatio_' + key)
41 | }
42 | })
43 |
44 | //sync defaults
45 | NSUserDefaults.standardUserDefaults().synchronize()
46 | }
47 |
48 | //get options
49 | let options = {}
50 | OPTIONS.map((key) => {
51 |
52 | //get options from user defaults
53 | let option = NSUserDefaults.standardUserDefaults().objectForKey('SketchAspectRatio_' + key)
54 |
55 | //convert to correct type and set
56 | if(option) {
57 | options[key] = parsePrimitives(String(option))
58 | }
59 | })
60 |
61 | return options
62 | }
63 |
64 | function parsePrimitives(value) {
65 |
66 | if (value == '') {
67 | return value
68 | } else if (value == 'true') {
69 | value = true
70 | } else if (value == 'false') {
71 | value = false
72 | } else if (value == 'null') {
73 | value = null
74 | } else if (value == 'undefined') {
75 | value = undefined
76 | } else if (!isNaN(value) && value != '') {
77 | value = parseFloat(value)
78 | }
79 |
80 | return value
81 | }
82 |
--------------------------------------------------------------------------------
/src/utils/setAspectRatioForSelection.js:
--------------------------------------------------------------------------------
1 | export function setAspectRatioForSelection (selection, ratio, keep, appendRatioToName) {
2 | selection.forEach(function (layer) {
3 | let frame = layer.frame()
4 | let width = frame.width()
5 | let height = frame.height()
6 |
7 | let ratioX = 1
8 | let ratioY = 1
9 |
10 | if (ratio) {
11 | ratioX = ratio[0]
12 | ratioY = ratio[1]
13 | }
14 |
15 | if (keep === 'height') {
16 | let newWidth = height * (ratioX / ratioY)
17 | layer.frame().setWidth(newWidth)
18 | } else {
19 | let newHeight = width / (ratioX / ratioY)
20 | layer.frame().setHeight(newHeight)
21 | }
22 |
23 | let name = layer.name()
24 |
25 | let nameSplit = name.split(' ')
26 |
27 | if (appendRatioToName === true) {
28 | if (nameSplit[nameSplit.length - 1].match('[1-9]+:[1-9]+')) {
29 | nameSplit[nameSplit.length - 1] = ratioX + ':' + ratioY
30 | } else {
31 | nameSplit.push(ratioX + ':' + ratioY)
32 | }
33 | } else {
34 | if (nameSplit[nameSplit.length - 1].match('[1-9]+:[1-9]+')) {
35 | nameSplit[nameSplit.length - 1] = ''
36 | }
37 | }
38 |
39 | name = nameSplit.join(' ').trim()
40 |
41 | layer.setName(name)
42 | })
43 | }
44 |
--------------------------------------------------------------------------------
/src/utils/sketch-deprecated.js:
--------------------------------------------------------------------------------
1 | export function hasMSArray () {
2 | return (typeof MSArray !== 'undefined')
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/sketch-dom.js:
--------------------------------------------------------------------------------
1 | export function arrayify (items) {
2 | if (!items) {
3 | return []
4 | }
5 | var length = items.count()
6 | var jsArray = []
7 | while (length--) {
8 | jsArray.push(items.objectAtIndex(length))
9 | }
10 | return jsArray
11 | }
12 |
13 | export function layersWithChildren (layers) {
14 | var items = []
15 | arrayify(layers).forEach(function (layer) {
16 | items.push(layer)
17 | items = items.concat(arrayify(layer.children()))
18 | })
19 | return items
20 | }
21 |
22 | export function parentPageForObject (object) {
23 | if (object && object.isKindOfClass(MSPage)) {
24 | return object
25 | } else if (object && object.parentGroup() != null) {
26 | return parentPageForObject(object.parentGroup())
27 | } else {
28 | return null
29 | }
30 | }
31 |
32 | export function parentArtboardForObject (object) {
33 | if (object && object.isKindOfClass(MSArtboardGroup)) {
34 | return object
35 | } else if (object && object.parentGroup() != null) {
36 | return parentArtboardForObject(object.parentGroup())
37 | } else {
38 | return null
39 | }
40 | }
41 |
42 | function getStyle (layer, styleType) {
43 | var style = layer.style()
44 | if (style[styleType]) {
45 | return style[styleType]()
46 | } else if (style[styleType + 's']) {
47 | return style[styleType + 's']()[0]
48 | }
49 | return null
50 | }
51 |
52 | // styleType: one of fill, border, innerShadow
53 | export function getStyleColor (layer, styleType) {
54 | var style = getStyle(layer, styleType)
55 | if (!style) {
56 | return null
57 | }
58 | if (style['color']) {
59 | return hexColor(style.color())
60 | } else if (style['colorGeneric']) {
61 | return hexColor(style.colorGeneric())
62 | }
63 | return null
64 | }
65 |
66 | export function setStyleColor (layer, styleType, hexValue) {
67 | var style = layer.style()
68 | if (!style[styleType]) {
69 | return null
70 | }
71 | style[styleType]().color = colorFromHex(hexValue)
72 | }
73 |
74 | export function findLayersInLayer (rootLayer, name, exactMatch, type, subLayersOnly, layersToExclude) {
75 | // create predicate format
76 | var formatRules = ['(name != NULL)']
77 | var predicateArguments = []
78 |
79 | if (name) {
80 | if (exactMatch) {
81 | formatRules.push('(name == %@)')
82 | } else {
83 | formatRules.push('(name like %@)')
84 | }
85 | predicateArguments.push(name)
86 | }
87 |
88 | if (type) {
89 | formatRules.push('(className == %@)')
90 | predicateArguments.push(type)
91 | } else {
92 | formatRules.push('(className == "MSLayerGroup" OR className == "MSShapeGroup" OR className == "MSArtboardGroup" OR className == "MSTextLayer")')
93 | }
94 |
95 | if (layersToExclude) {
96 | formatRules.push('NOT (SELF IN %@)')
97 | predicateArguments.push(layersToExclude)
98 | }
99 |
100 | var formatString = formatRules.join(' AND ')
101 | var predicate = NSPredicate.predicateWithFormat_argumentArray(formatString, predicateArguments)
102 |
103 | // get layers to filter
104 | var layers
105 | if (subLayersOnly) {
106 | layers = rootLayer.layers().array()
107 | } else {
108 | layers = rootLayer.children()
109 | }
110 |
111 | var queryResult = layers.filteredArrayUsingPredicate(predicate)
112 | return arrayify(queryResult)
113 | }
114 |
--------------------------------------------------------------------------------
/src/utils/sketch-ui.js:
--------------------------------------------------------------------------------
1 | export function createSelect (items, selectedIndex, frame) {
2 | var select = NSPopUpButton.alloc().initWithFrame_pullsDown(frame, false)
3 | select.addItemsWithTitles(items)
4 | select.selectItemAtIndex(selectedIndex)
5 | return select
6 | }
7 |
8 | export function createComboBox (items, selectedIndex, frame, pullsDown) {
9 | var select = NSComboBox.alloc().initWithFrame(frame)
10 | select.numberOfVisibleItems = 12
11 | select.completes = true
12 |
13 | select.addItemsWithObjectValues(items)
14 | select.selectItemAtIndex(selectedIndex)
15 | return select
16 | }
17 |
18 | export function createAlert (context, title, message, iconFilePath) {
19 | var alert = COSAlertWindow.new()
20 | alert.setMessageText(title)
21 | alert.setInformativeText(message)
22 |
23 | if (iconFilePath) {
24 | // get icon path
25 | let iconUrl = context.plugin.urlForResourceNamed(iconFilePath)
26 |
27 | // set icon
28 | let icon = NSImage.alloc().initByReferencingFile(iconUrl.path())
29 | alert.setIcon(icon)
30 | }
31 |
32 | return alert
33 | }
34 |
35 | export function createLabel (text, frame, fontSize) {
36 | var label = NSTextField.alloc().initWithFrame(frame)
37 | label.setStringValue(text)
38 |
39 | label.setFont(NSFont.boldSystemFontOfSize(fontSize))
40 |
41 | label.setBezeled(false)
42 | label.setDrawsBackground(false)
43 | label.setEditable(false)
44 | label.setSelectable(false)
45 |
46 | return label
47 | }
48 |
--------------------------------------------------------------------------------