├── .gitignore
├── images
├── icon.sketch
├── sketch-aspect-ratio.png
├── set-aspect-ratio-dialog.png
└── sketch-aspect-ratio.sketch
├── src
├── resources
│ ├── icon.png
│ └── ratios.js
├── utils
│ ├── sketch-deprecated.js
│ ├── setAspectRatioForSelection.js
│ ├── sketch-ui.js
│ ├── options.js
│ └── sketch-dom.js
├── commands
│ ├── index.js
│ ├── setAspectRatioRepeat.js
│ ├── setAspectRatioList.js
│ └── setAspectRatioManual.js
└── plugin.js
├── .sketchpacks.json
├── Sketch Aspect Ratio.sketchplugin
└── Contents
│ ├── Resources
│ ├── icon.png
│ ├── ratios copy.js
│ └── ratios.js
│ └── Sketch
│ ├── manifest.json
│ └── plugin.js
├── LICENSE
├── README.md
├── package.json
└── gulpfile.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 | /build/
4 | /src/custom-ratios/
5 |
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/images/icon.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dataliterate/sketch-aspect-ratio/HEAD/images/icon.sketch
--------------------------------------------------------------------------------
/src/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dataliterate/sketch-aspect-ratio/HEAD/src/resources/icon.png
--------------------------------------------------------------------------------
/src/utils/sketch-deprecated.js:
--------------------------------------------------------------------------------
1 | export function hasMSArray () {
2 | return (typeof MSArray !== 'undefined')
3 | }
4 |
--------------------------------------------------------------------------------
/images/sketch-aspect-ratio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dataliterate/sketch-aspect-ratio/HEAD/images/sketch-aspect-ratio.png
--------------------------------------------------------------------------------
/images/set-aspect-ratio-dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dataliterate/sketch-aspect-ratio/HEAD/images/set-aspect-ratio-dialog.png
--------------------------------------------------------------------------------
/images/sketch-aspect-ratio.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dataliterate/sketch-aspect-ratio/HEAD/images/sketch-aspect-ratio.sketch
--------------------------------------------------------------------------------
/.sketchpacks.json:
--------------------------------------------------------------------------------
1 | {
2 | "schema_version": "1.0.0",
3 | "manifest_path": "Sketch%20Aspect%20Ratio.sketchplugin/Contents/Sketch/manifest.json"
4 | }
5 |
--------------------------------------------------------------------------------
/Sketch Aspect Ratio.sketchplugin/Contents/Resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dataliterate/sketch-aspect-ratio/HEAD/Sketch Aspect Ratio.sketchplugin/Contents/Resources/icon.png
--------------------------------------------------------------------------------
/src/resources/ratios.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 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 |
--------------------------------------------------------------------------------
/src/commands/index.js:
--------------------------------------------------------------------------------
1 | import setAspectRatioManual from './setAspectRatioManual'
2 | import setAspectRatioList from './setAspectRatioList'
3 | import setAspectRatioRepeat from './setAspectRatioRepeat'
4 |
5 | export { setAspectRatioManual, setAspectRatioList, setAspectRatioRepeat }
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/commands/setAspectRatioRepeat.js:
--------------------------------------------------------------------------------
1 | import { setAspectRatioForSelection } from '../utils/setAspectRatioForSelection'
2 | import Options, * as OPTIONS from '../utils/options'
3 |
4 | export default function setAspectRatioRepeat (context) {
5 | let selection = context.selection
6 |
7 | if (!selection.firstObject()) {
8 | context.document.showMessage('Please select one or more layers')
9 | return
10 | }
11 |
12 | // Load Options
13 | let options = Options()
14 |
15 | let ratio1 = options[OPTIONS.RATIO_1] || 1
16 | let ratio2 = options[OPTIONS.RATIO_2] || 1
17 | let keep = options[OPTIONS.KEEP] || 'width'
18 | let rename = options[OPTIONS.RENAME] || false
19 |
20 | if (rename == 1) { rename = true} else if (rename == 0) { rename = false }
21 |
22 | log(rename)
23 |
24 | setAspectRatioForSelection(selection, [ratio1, ratio2], keep, rename)
25 | }
26 |
--------------------------------------------------------------------------------
/src/plugin.js:
--------------------------------------------------------------------------------
1 | import * as commands from './commands'
2 |
3 | export const HKSketchFusionExtension = {
4 | name: 'Aspect Ratio',
5 | bundleName: 'Sketch Aspect Ratio',
6 | description: 'Set preset or custom aspect ratios for a selected layer.',
7 | author: 'CL/precious design studio',
8 | authorEmail: 'christoph.labacher@precious-forever.com',
9 | version: '1.0.1',
10 | identifier: 'com.precious-forever.sketch-aspect-ratio',
11 | menu: {
12 | 'isRoot': false,
13 | 'items': [
14 | 'setAspectRatioList',
15 | 'setAspectRatioManual',
16 | 'setAspectRatioRepeat'
17 | ]
18 | },
19 | commands: {
20 | setAspectRatioRepeat: {
21 | name: 'Repeat with last options',
22 | shortcut: '',
23 | run: commands.setAspectRatioRepeat
24 | },
25 | setAspectRatioManual: {
26 | name: 'Set Aspect Ratio manually',
27 | shortcut: '',
28 | run: commands.setAspectRatioManual
29 | },
30 | setAspectRatioList: {
31 | name: 'Set Aspect Ratio',
32 | shortcut: '',
33 | run: commands.setAspectRatioList
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
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 |
--------------------------------------------------------------------------------
/src/utils/options.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Options library
3 | *
4 | * Provides functionality to get and set user options shared across the plugin.
5 | */
6 |
7 |
8 | export const RATIO_1 = 'ratio1'
9 | export const RATIO_2 = 'ratio2'
10 |
11 | export const RATIO_SELECTION = 'ratioSelection'
12 |
13 | export const RATIO_MANUAL_1 = 'ratioManual1'
14 | export const RATIO_MANUAL_2 = 'ratioManual2'
15 |
16 | export const KEEP_SELECTION = 'keepSelection'
17 | export const KEEP = 'keep'
18 |
19 | export const RENAME_SELECTION = 'renameSelection'
20 | export const RENAME = 'rename'
21 |
22 | let OPTIONS = [
23 | RATIO_SELECTION, RATIO_1, RATIO_2, RATIO_MANUAL_1, RATIO_MANUAL_2, KEEP_SELECTION, KEEP, RENAME_SELECTION, RENAME
24 | ]
25 |
26 |
27 | /**
28 | * Gets or sets the stored options in user defaults.
29 | *
30 | * @returns {Object}
31 | */
32 | export default function(newOptions) {
33 |
34 | //set new options
35 | if(newOptions) {
36 | OPTIONS.forEach((key) => {
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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sketch-ascpect-ratio",
3 | "version": "1.0.1",
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 | "repository": "https://github.com/preciousforever/sketch-aspect-ratio",
8 | "main": "src/plugin.js",
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1",
11 | "lint": "standard 'src/**/*.js'"
12 | },
13 | "license": "MIT",
14 | "devDependencies": {
15 | "async": "^1.5.2",
16 | "babel": "^6.3.26",
17 | "babel-cli": "^6.4.5",
18 | "babel-plugin-sketch-manifest-processor": "^0.1.1",
19 | "babel-preset-es2015": "^6.3.13",
20 | "babelify": "^7.2.0",
21 | "browserify": "^13.0.0",
22 | "del": "^2.2.0",
23 | "expand-tilde": "^1.2.0",
24 | "fs-extra": "^0.26.5",
25 | "gulp": "^3.9.0",
26 | "gulp-babel": "^6.1.2",
27 | "gulp-github-release": "^1.1.2",
28 | "gulp-zip": "^3.2.0",
29 | "lodash": "^4.2.1",
30 | "minimist": "^1.2.0",
31 | "run-sequence": "^1.1.5",
32 | "standard": "^7.1.2",
33 | "vinyl-source-stream": "^1.1.0",
34 | "gulp-sketch": "^1.0.3"
35 | },
36 | "standard": {
37 | "ignore": [
38 | "src/vendor/"
39 | ],
40 | "globals": [
41 | "NSURL",
42 | "NSView",
43 | "NSMakeRect",
44 | "NSString",
45 | "NSUTF8StringEncoding",
46 | "MSShapeGroup",
47 | "MSArtboardGroup",
48 | "MSRectangleShape",
49 | "MSShapeGroup",
50 | "MSColor",
51 | "MSArray",
52 | "MSColor",
53 | "NSApp",
54 | "MSTextLayer",
55 | "NSWorkspace",
56 | "NSTextField",
57 | "STYLE_ICONS",
58 | "NSRunningApplication",
59 | "NSApplicationActivateAllWindows",
60 | "NSFileManager",
61 | "NSHomeDirectory",
62 | "NSUserDefaults",
63 | "NSURLIsDirectoryKey",
64 | "NSURLNameKey",
65 | "NSURLPathKey",
66 | "NSDirectoryEnumerationSkipsHiddenFiles",
67 | "MOPointer",
68 | "MSPage",
69 | "NSPredicate",
70 | "NSPopUpButton",
71 | "NSComboBox",
72 | "COSAlertWindow",
73 | "NSFont",
74 | "NSImage"
75 | ]
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/commands/setAspectRatioList.js:
--------------------------------------------------------------------------------
1 | import { setAspectRatioForSelection } from '../utils/setAspectRatioForSelection'
2 | import { createAlert, createLabel, createSelect } from '../utils/sketch-ui'
3 | import * as ratios from '../resources/ratios.js'
4 |
5 | import Options, * as OPTIONS from '../utils/options'
6 |
7 | export default function setAspectRatioList (context) {
8 | let selection = context.selection
9 |
10 | if (!selection.firstObject()) {
11 | context.document.showMessage('Please select one or more layers')
12 | return
13 | }
14 |
15 | // Load Options
16 | let options = Options()
17 |
18 | let ratioSelection = options[OPTIONS.RATIO_SELECTION] || 0
19 | let keepSelection = options[OPTIONS.KEEP_SELECTION] || 0
20 | let renameSelection = options[OPTIONS.RENAME_SELECTION] || 0
21 |
22 | var alert = createAlert(context, 'Set Aspect Ratio', 'Change the aspect ratio of the selected layers', 'icon.png')
23 | var listView = NSView.alloc().initWithFrame(NSMakeRect(0, 0, 300, 120))
24 |
25 | var ratioValues = []
26 |
27 | ratios.ratios.forEach(function (ratio) {
28 | ratioValues.push(ratio.x + ':' + ratio.y)
29 | })
30 |
31 | var uiSelectRatio = createSelect(ratioValues, ratioSelection, NSMakeRect(0, 98, 250, 22), true)
32 | listView.addSubview(uiSelectRatio)
33 |
34 | var keepValues = ['Width', 'Height']
35 | var uiSelectKeep = createSelect(keepValues, keepSelection, NSMakeRect(150, 40, 100, 22), true)
36 | listView.addSubview(createLabel('Keep', NSMakeRect(0, 40, 80, 22), 12, true))
37 | listView.addSubview(uiSelectKeep)
38 |
39 | var renameValues = ['Yes', 'No']
40 | var uiSelectRename = createSelect(renameValues, renameSelection, NSMakeRect(150, 10, 100, 22), true)
41 | listView.addSubview(createLabel('Append Ratio to Name', NSMakeRect(0, 10, 140, 22), 12, true))
42 | listView.addSubview(uiSelectRename)
43 |
44 | alert.addAccessoryView(listView)
45 | alert.addButtonWithTitle('Change Aspect Ratio')
46 | alert.addButtonWithTitle('Cancel')
47 |
48 | var responseCode = alert.runModal()
49 | if (responseCode != '1000') { // eslint-disable-line eqeqeq
50 | return
51 | }
52 |
53 | var ratioValueIndex = uiSelectRatio.indexOfSelectedItem()
54 |
55 | var keepValueIndex = uiSelectKeep.indexOfSelectedItem()
56 | var renameValueIndex = uiSelectRename.indexOfSelectedItem()
57 |
58 | var ratio1 = ratios.ratios[ratioValueIndex].x
59 | var ratio2 = ratios.ratios[ratioValueIndex].y
60 |
61 | var keep = 'width'
62 | if (keepValueIndex === 1) {
63 | keep = 'height'
64 | }
65 |
66 | var rename = true
67 | if (renameValueIndex === 1) {
68 | rename = false
69 | }
70 |
71 | options[OPTIONS.RATIO_SELECTION] = uiSelectRatio.indexOfSelectedItem()
72 | options[OPTIONS.RATIO_1] = ratio1
73 | options[OPTIONS.RATIO_2] = ratio2
74 | options[OPTIONS.KEEP_SELECTION] = uiSelectKeep.indexOfSelectedItem()
75 | options[OPTIONS.KEEP] = keep
76 | options[OPTIONS.RENAME_SELECTION] = uiSelectRename.indexOfSelectedItem()
77 | options[OPTIONS.RENAME] = rename
78 |
79 | // Save Options
80 | Options(options)
81 |
82 | setAspectRatioForSelection(selection, [ratio1, ratio2], keep, rename)
83 | }
84 |
--------------------------------------------------------------------------------
/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/commands/setAspectRatioManual.js:
--------------------------------------------------------------------------------
1 | import { setAspectRatioForSelection } from '../utils/setAspectRatioForSelection'
2 | import { createAlert, createLabel, createSelect } from '../utils/sketch-ui'
3 |
4 | import Options, * as OPTIONS from '../utils/options'
5 |
6 | export default function setAspectRatioManual (context) {
7 | let selection = context.selection
8 |
9 | if (!selection.firstObject()) {
10 | context.document.showMessage('Please select one or more layers')
11 | return
12 | }
13 |
14 | // Load Options
15 | let options = Options()
16 |
17 | let ratio1Selection = options[OPTIONS.RATIO_MANUAL_1] - 1 || 0
18 | let ratio2Selection = options[OPTIONS.RATIO_MANUAL_2] - 1 || 0
19 | let keepSelection = options[OPTIONS.KEEP_SELECTION] || 0
20 | let renameSelection = options[OPTIONS.RENAME_SELECTION] || 0
21 |
22 | var alert = createAlert(context, 'Set Aspect Ratio', 'Change the aspect ratio of the selected layers', 'icon.png')
23 | var listView = NSView.alloc().initWithFrame(NSMakeRect(0, 0, 300, 120))
24 |
25 | var ratioValues = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20']
26 | var uiSelectRatio1 = createSelect(ratioValues, ratio1Selection, NSMakeRect(0, 98, 100, 22), true)
27 | var uiSelectRatio2 = createSelect(ratioValues, ratio2Selection, NSMakeRect(150, 98, 100, 22), true)
28 | listView.addSubview(createLabel('to', NSMakeRect(115, 98, 20, 22), 12, true))
29 | listView.addSubview(uiSelectRatio1)
30 | listView.addSubview(uiSelectRatio2)
31 |
32 | var keepValues = ['Width', 'Height']
33 | var uiSelectKeep = createSelect(keepValues, keepSelection, NSMakeRect(150, 40, 100, 22), true)
34 | listView.addSubview(createLabel('Keep', NSMakeRect(0, 40, 80, 22), 12, true))
35 | listView.addSubview(uiSelectKeep)
36 |
37 | var renameValues = ['Yes', 'No']
38 | var uiSelectRename = createSelect(renameValues, renameSelection, NSMakeRect(150, 10, 100, 22), true)
39 | listView.addSubview(createLabel('Append Ratio to Name', NSMakeRect(0, 10, 140, 22), 12, true))
40 | listView.addSubview(uiSelectRename)
41 |
42 | alert.addAccessoryView(listView)
43 | alert.addButtonWithTitle('Change Aspect Ratio')
44 | alert.addButtonWithTitle('Cancel')
45 |
46 | var responseCode = alert.runModal()
47 | if (responseCode != '1000') { // eslint-disable-line eqeqeq
48 | return
49 | }
50 |
51 | var ratioValue1Index = uiSelectRatio1.indexOfSelectedItem()
52 | var ratioValue2Index = uiSelectRatio2.indexOfSelectedItem()
53 |
54 | var keepValueIndex = uiSelectKeep.indexOfSelectedItem()
55 | var renameValueIndex = uiSelectRename.indexOfSelectedItem()
56 |
57 | var ratio1 = ratioValue1Index + 1
58 | var ratio2 = ratioValue2Index + 1
59 |
60 | var keep = 'width'
61 | if (keepValueIndex === 1) {
62 | keep = 'height'
63 | }
64 |
65 | var rename = true
66 | if (renameValueIndex === 1) {
67 | rename = false
68 | }
69 |
70 | options[OPTIONS.RATIO_MANUAL_1] = ratio1
71 | options[OPTIONS.RATIO_MANUAL_2] = ratio2
72 | options[OPTIONS.RATIO_1] = ratio1
73 | options[OPTIONS.RATIO_2] = ratio2
74 | options[OPTIONS.KEEP_SELECTION] = uiSelectKeep.indexOfSelectedItem()
75 | options[OPTIONS.KEEP] = keep
76 | options[OPTIONS.RENAME_SELECTION] = uiSelectRename.indexOfSelectedItem()
77 | options[OPTIONS.RENAME] = rename
78 |
79 | // Save Options
80 | Options(options)
81 |
82 | setAspectRatioForSelection(selection, [ratio1, ratio2], keep, rename)
83 | }
84 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var browserify = require('browserify');
3 | var babelify = require('babelify');
4 | var source = require('vinyl-source-stream');
5 | var fs = require('fs');
6 | var path = require('path');
7 | var runSequence = require('run-sequence');
8 | var del = require('del');
9 | var async = require('async');
10 | var fse = require('fs-extra');
11 | var expandTilde = require('expand-tilde');
12 |
13 | var release = require('gulp-github-release');
14 | var zip = require('gulp-zip');
15 |
16 | var minimist = require('minimist');
17 |
18 | var sketch = require('gulp-sketch');
19 |
20 | var knownOptions = {
21 | string: 'target',
22 | default: { target: 'production' }
23 | };
24 |
25 | var options = minimist(process.argv.slice(2), knownOptions);
26 |
27 | console.log("Gulp is setup for target: " + options.target);
28 |
29 | var SKETCH_PLUGINS_FOLDER = path.join(expandTilde('~'),'/Library/Application Support/com.bohemiancoding.sketch3/Plugins');
30 |
31 | var ManifestProcessorOptions = {
32 | pluginManifestDescriberName: 'HKSketchFusionExtension',
33 | startingManifestTag: '__$begin_of_manifest_\n',
34 | endingManifestTag: '__$end_of_manifest_\n',
35 | scriptFileName: 'plugin.js',
36 | globalVarName: '__globals'
37 | };
38 |
39 | var currentManifest = {};
40 |
41 | //export icon
42 | gulp.task('icon', function(){
43 | return gulp.src('./images/icon.sketch')
44 | .pipe(sketch({
45 | export: 'artboards',
46 | formats: 'png'
47 | }))
48 | .pipe(gulp.dest('./src/resources/'));
49 | });
50 |
51 | //export logo
52 | gulp.task('logo', function(){
53 | return gulp.src('./images/sketch-aspect-ratio.sketch')
54 | .pipe(sketch({
55 | export: 'artboards',
56 | formats: 'png'
57 | }))
58 | .pipe(gulp.dest('./images/'));
59 | });
60 |
61 | gulp.task('assets',function(callback) {
62 | runSequence('icon','logo',callback);
63 | });
64 |
65 | function extractManifestObject() {
66 | var data = fs.readFileSync(path.join(__dirname,'build',ManifestProcessorOptions.scriptFileName),'utf8');
67 | var startTag = ManifestProcessorOptions.startingManifestTag;
68 | var endTag = ManifestProcessorOptions.endingManifestTag;
69 |
70 | var startIndex = data.indexOf(startTag);
71 | var endIndex = data.indexOf(endTag);
72 |
73 | if(startIndex === -1 || endIndex === -1) {
74 | return;
75 | }
76 |
77 | return JSON.parse(data.substring(startIndex+startTag.length,endIndex));
78 | }
79 |
80 | gulp.task('clean', function () {
81 | return del(['build/**','dist/**']);
82 | });
83 |
84 | gulp.task('prepare-manifest',function(callback) {
85 | var manifest = extractManifestObject();
86 |
87 | // manipulate manifest for sketch
88 | if (options.target === 'beta') {
89 | manifest.version = manifest.version + '-beta';
90 | manifest.identifier = manifest.identifier + '-beta';
91 | manifest.bundleName = manifest.bundleName + manifest.version;
92 | manifest.name = manifest.name + ' (' + manifest.version + ')';
93 | }
94 |
95 | fse.outputJsonSync(path.join(__dirname,'build/manifest.json'),manifest);
96 | currentManifest = manifest;
97 | callback(null);
98 | });
99 |
100 | gulp.task('prepare-folders',function(callback) {
101 | async.parallel({
102 | build: function(callback) {
103 | fse.ensureDir(path.join(__dirname,'build'),callback);
104 | },
105 | dist: function(callback) {
106 | fse.ensureDir(path.join(__dirname,'dist'),callback);
107 | }
108 | },callback);
109 | });
110 |
111 |
112 | gulp.task('assemble-plugin-bundle',function(callback) {
113 |
114 | function normalizePluginFileName(name) {
115 | return name;
116 | }
117 |
118 | var bundlePath = path.join(__dirname,'dist',normalizePluginFileName(currentManifest.bundleName || currentManifest.name) + '.sketchplugin');
119 |
120 | async.parallel({
121 | manifest: function(callback) {
122 | fse.outputJson(path.join(bundlePath,'Contents','Sketch','manifest.json'),currentManifest,callback);
123 | },
124 | runtime: function(callback) {
125 | var script = fs.readFileSync(path.join(__dirname,'build',ManifestProcessorOptions.scriptFileName),'utf8');
126 | script = ["var "+ManifestProcessorOptions.globalVarName+" = this;",script].join("");
127 |
128 | fse.outputFile(path.join(bundlePath,'Contents','Sketch',ManifestProcessorOptions.scriptFileName),script,callback);
129 | }
130 | },function(err,data) {
131 | callback(null);
132 | });
133 | });
134 |
135 | gulp.task('assemble-plugin-resources',function(callback) {
136 | function normalizePluginFileName(name) {
137 | return name;
138 | }
139 | return gulp.src('src/resources/**/*.*')
140 | .pipe(gulp.dest(path.join(__dirname,'dist',normalizePluginFileName(currentManifest.bundleName || currentManifest.name)+'.sketchplugin','Contents/Resources')));
141 | });
142 |
143 | gulp.task('install-plugin',function(){
144 | return gulp.src("dist/**/*.*")
145 | .pipe(gulp.dest(SKETCH_PLUGINS_FOLDER));
146 | });
147 |
148 | gulp.task('build',function(callback) {
149 | runSequence('clean','prepare-folders','bundle','prepare-manifest','assemble-plugin-bundle','assemble-plugin-resources','install-plugin',callback);
150 | });
151 |
152 | gulp.task('bundle',function() {
153 | var filePath = './src/plugin.js';
154 | var extensions = ['.js'];
155 |
156 | var bundler = browserify({
157 | entries: [filePath],
158 | extensions: extensions,
159 | debug: false
160 | });
161 |
162 | bundler.transform(babelify.configure({
163 | presets: ["es2015"],
164 | plugins: [["babel-plugin-sketch-manifest-processor",ManifestProcessorOptions]]
165 | }));
166 |
167 | return bundler.bundle()
168 | .pipe(source(ManifestProcessorOptions.scriptFileName))
169 | .pipe(gulp.dest('./build/'));
170 | });
171 |
172 |
173 | gulp.task('watch', function(){
174 | runSequence('build', function() {
175 | gulp.watch('./src/**/*.*',function() {
176 | console.log("Watching...");
177 | runSequence('clean','build',function(){
178 | console.log("Rebuild complete!");
179 | });
180 | });
181 | });
182 | });
183 |
184 | gulp.task('default',function(callback) {
185 | runSequence('assets','build', callback);
186 | });
187 |
188 | gulp.task('zip', ['build'], function() {
189 | return gulp.src('./dist/*.sketchplugin/**/*')
190 | .pipe(zip('SketchAspectRatio.zip'))
191 | .pipe(gulp.dest('dist'))
192 | });
193 |
194 | gulp.task('release', ['zip'], function() {
195 | return gulp.src('./dist/SketchAspectRatio.zip')
196 | .pipe(release({
197 | //token: 'token', // or you can set an env var called GITHUB_TOKEN instead
198 | owner: 'preciousforever', // if missing, it will be extracted from manifest (the repository.url field)
199 | repo: 'sketch-aspect-ratio', // if missing, it will be extracted from manifest (the repository.url field)
200 | //tag: 'v1.0.0', // if missing, the version will be extracted from manifest and prepended by a 'v'
201 | //name: 'publish-release v1.0.0', // if missing, it will be the same as the tag
202 | //notes: 'very good!', // if missing it will be left undefined
203 | draft: false, // if missing it's false
204 | prerelease: false, // if missing it's false
205 | manifest: require('./build/manifest.json') // package.json from which default values will be extracted if they're missing
206 | }));
207 | });
208 |
--------------------------------------------------------------------------------
/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