Repository: tylergaw/day-player Branch: develop Commit: 357566230d3f Files: 26 Total size: 29.6 KB Directory structure: gitextract_gh6j5y9l/ ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── Makefile ├── README.md ├── manifest.json ├── package.json ├── resources/ │ ├── fillmurray.icns │ ├── lorempixel.icns │ ├── placecage.icns │ ├── placeholdit.icns │ ├── placekitten.icns │ └── unsplashit.icns ├── scripts/ │ └── release.js └── src/ ├── components/ │ ├── Alert.js │ ├── Label.js │ ├── PopUpButton.js │ └── TextField.js ├── handlers/ │ ├── fillMurray.js │ ├── placeCage.js │ ├── placeHoldIt.js │ ├── placeKitten.js │ └── unsplashIt.js ├── index.cocoascript └── utils.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc ================================================ { "ecmaFeatures": { "modules": true, "experimentalObjectRestSpread": true }, "rules": { "accessor-pairs": 0, "comma-dangle": [1, "never"], "comma-spacing": [1, {"before": false, "after": true}], "comma-style": [2, "last"], "consistent-return": 2, "dot-location": [ 2, "property" ], "dot-notation": 2, "eol-last": 2, "indent": [ 2, 2, { "SwitchCase": 1 } ], "keyword-spacing": 2, "no-bitwise": 0, "no-console": 2, "no-const-assign": 2, "no-debugger": 2, "no-empty": 2, 'no-missing-import': 0, "no-multi-spaces": 2, "no-shadow": 0, "no-undef": 2, "no-unreachable": 1, "no-unused-expressions": 2, "no-unused-vars": [ 2, { "args": "none", "varsIgnorePattern": "_", "argsIgnorePattern": "_" } ], "object-curly-spacing": [1, "never"], "quotes": [ 1, "single", "avoid-escape" ], "semi": 2, "space-before-blocks": 2, "space-in-parens": [1, "never"], "strict": [ 2, "never" ] }, "env": { "es6": true }, "globals": { Alert: true, Label: true, PopUpButton: true, TextField: true, createConfirmHandler: true, createPluginHandler: true, lowLevelSetImage: true, NSAlert: true, NSView: true, NSMakeRect: true, NSImage: true, NSURL: true, MSImageData: true, NSTextField: true, NSPopUpButton: true, log: true } } ================================================ FILE: .gitignore ================================================ npm-debug.log node_modules/ Day Player.sketchplugin DayPlayer.zip ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - 5.12.0 sudo: false cache: directories: - /home/travis/.local - node_modules - $(npm config get cache) before_install: - npm config set progress false - npm config set spin false install: - pip install --user -U awscli - npm install script: - make lint deploy: - provider: script script: make publish on: tags: true env: global: - AWS_ACCESS_KEY_ID=AKIAIRMRYU635FO4D26A - secure: PqKc8FFSOiIQvEMkQtPv9b55NwdMreHEF4njCnnk/pcBgUIep8Ew2JYmdp/KuSx1gyZBRys0hJqSTJ6FVZrCkI7pHD3bkKrNGq807c5yVFHtTzTUoQZ62zYEC/fnsbvKdd6sGtznABB1boiuiV1Crj0v2gtkA/kUJ431XUFqBw4= ================================================ FILE: LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2016 Tyler Gaw Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ PLUGIN_PATH := ~/Library/Application\ Support/com.bohemiancoding.sketch3/Plugins/ PLUGIN_DIR := Day\ Player.sketchplugin PLUGIN_NAME := Day Player ZIP_NAME := DayPlayer.zip S3_BUCKET := s3://day-player SRC_DIR := ./src RESOURCES_DIR := ./resources LICENSE := LICENSE.md MANIFEST := manifest.json clean: @rm -rf $(PLUGIN_DIR) @rm -f $(ZIP_NAME) @rm -rf $(PLUGIN_PATH)$(PLUGIN_DIR) build: @make clean @mkdir $(PLUGIN_DIR) @mkdir $(PLUGIN_DIR)/Contents @cp $(LICENSE) $(PLUGIN_DIR)/Contents/$(LICENSE) @cp -r $(RESOURCES_DIR) $(PLUGIN_DIR)/Contents/Resources @cp -r $(SRC_DIR) $(PLUGIN_DIR)/Contents/Sketch @cp -r $(MANIFEST) $(PLUGIN_DIR)/Contents/Sketch/$(MANIFEST) install: @make clean @echo "Installing $(PLUGIN_NAME)..." @make build @mv $(PLUGIN_DIR) $(PLUGIN_PATH) @echo "$(PLUGIN_NAME) installed" package: make build @zip -rm $(ZIP_NAME) $(PLUGIN_DIR) @echo "$(ZIP_NAME) created" changed: @echo "Changes detected..." @make install lint: @echo "Linting with eslint..." @./node_modules/.bin/eslint ./src/**/*.js watch: @echo "Watching src directory for changes..." @./node_modules/.bin/watch 'make changed' ./src ./resources release: @node scripts/release.js publish: @make package @aws s3 cp $(ZIP_NAME) $(S3_BUCKET)/releases/DayPlayer-$(TRAVIS_TAG).zip ================================================ FILE: README.md ================================================ [![Day Player Download](https://d3vv6lp55qjaqc.cloudfront.net/items/1u3H0M0L1j281F0R2E39/dayplayer-sketch.png) Download the latest version (3.0.0)](http://day-player.s3-website-us-east-1.amazonaws.com/releases/DayPlayer-3.0.0.zip) ## Day Player [![Build Status](https://travis-ci.org/tylergaw/day-player.svg)](https://travis-ci.org/tylergaw/day-player) A Sketch Plugin for creating placeholder images from online services. ## Installation - [ Download the latest version (3.0.0)](http://day-player.s3-website-us-east-1.amazonaws.com/releases/DayPlayer-3.0.0.zip) - Unzip the file - Double-click Day Player.sketchplugin to install ## What Does It Do? It allows you to insert customized placeholder images into any Sketch document from a number of different placeholder image services: - [unsplash.it](http://unsplash.it/) - [placehold.it](http://placehold.it/) - [fillmurray.com](http://www.fillmurray.com/) - [placecage.com](http://www.placecage.com/) - [placekitten.com](http://placekitten.com/) ## How to use it - Open a new or existing Sketch document - Plugins > Day Player > Service... - Update the options to your liking, OK/Enter - The image is created on the canvas ![Animated gif showing basic Day Player usage](https://d3vv6lp55qjaqc.cloudfront.net/items/1q2S3E2B333G2m382A1v/Screen%20Recording%202016-11-13%20at%2001.52%20PM.gif) ### Appending images to an Artboard or Group - Open a new or existing Sketch document - Select the Artboard or Group - Plugins > Day Player > Service... - Update the options to your liking, OK/Enter - The image is created within the Artboard or Group - Image will be placed in the top left corner of the Artboard or Group **Artboard:** ![Animated gif showing Day Player usage on Artboard](https://d3vv6lp55qjaqc.cloudfront.net/items/2P1n0t0H1o0y0E1J1I3t/Screen%20Recording%202016-11-13%20at%2001.58%20PM.gif) **Group:** ![Animated gif showing Day Player usage on Group](https://d3vv6lp55qjaqc.cloudfront.net/items/1y2c3Z3m3K2F0b1T2720/Screen%20Recording%202016-11-13%20at%2003.20%20PM.gif) ### Creating images with dimensions and position of existing Layers - Open a new or existing Sketch document - Select the desired Layer - Plugins > Day Player > Service... - Update the options to your liking, OK/Enter - The image is created in the parent group of the selected layer - Image will inherit the x, y, width, and height of the selected layer ![Animated gif showing Day Player existing layer usage](https://d3vv6lp55qjaqc.cloudfront.net/items/0o1M3n07223o223D2C3R/Screen%20Recording%202016-11-13%20at%2003.23%20PM.gif) ### Advanced Service Options All services offer width, height, and black & white / color options. Unsplash.it and Placehold.it offer further options to customize the placeholder images. **Unsplash.it:** ![Animated gif showing Day Player Unsplash.it usage](https://d3vv6lp55qjaqc.cloudfront.net/items/3a1g161P1S0r0J2g030Y/Screen%20Recording%202016-11-13%20at%2002.15%20PM.gif) **Placehold.it:** ![Animated gif showing Day Player Placehold.it usage](https://d3vv6lp55qjaqc.cloudfront.net/items/2h0x3M1S250N1i1g081M/Screen%20Recording%202016-11-13%20at%2003.26%20PM.gif) ------- ## Contributing to this project As with most open source projects, pull requests for bug fixes, and new functionality are always welcome. Prerequisites - Node `5.x.x`+ Fork this repo and clone a local copy of your fork. Install dependencies: ``` npm install ``` Create necessary application bundle from source by running: ``` make install ``` Watch the `src` and `resources` directories and recompile when changes are made by running: ``` make watch ``` `make install` and `make watch` will copy the application bundle to the default Sketch plugins location `~/Library/Application Support/com.bohemiancoding.sketch3/Plugins/` as `Day Player.sketchplugin`. See the `Makefile` for further details on the build process. ================================================ FILE: manifest.json ================================================ { "name": "Day Player", "description": "A Plugin for creating images from placeholder services.", "author": "Tyler Gaw", "homepage": "https://github.com/tylergaw/day-player", "version": "3.0.0", "identifier": "com.sketchapp.tylergaw.day-player", "compatibleVersion": 41, "commands" : [ { "name": "Unsplash.it", "script": "index.cocoascript", "handler": "unsplashIt", "identifier": "unsplash-it" }, { "name": "Fill Murray", "script": "index.cocoascript", "handler": "fillMurray", "identifier": "fill-murray" }, { "name": "Place Cage", "script": "index.cocoascript", "handler": "placeCage", "identifier": "place-cage" }, { "name": "Placehold.it", "script": "index.cocoascript", "handler": "placeHoldIt", "identifier": "place-hold-it" }, { "name": "Placekitten", "script": "index.cocoascript", "handler": "placeKitten", "identifier": "place-kitten" } ], "menu": { "title": "Day Player", "items": [ "unsplash-it", "place-hold-it", "fill-murray", "place-cage", "place-kitten" ] } } ================================================ FILE: package.json ================================================ { "name": "day-player-plugin-sketchapp", "version": "3.0.0", "description": "A Sketch Plugin for creating placeholder images", "repository": { "type": "git", "url": "git+https://github.com/tylergaw/day-player.git" }, "scripts": { "test": "make lint" }, "keywords": [ "sketch", "placeholder", "images" ], "author": "Tyler Gaw", "license": "MIT", "bugs": { "url": "https://github.com/tylergaw/day-player/issues" }, "homepage": "https://github.com/tylergaw/day-player#readme", "devDependencies": { "eslint": "3.8.0", "pre-commit": "1.1.3", "prompt": "1.0.0", "watch": "1.0.1" } } ================================================ FILE: scripts/release.js ================================================ /* eslint-env node */ /* eslint-disable strict, no-console */ 'use strict'; const version = require('../package.json').version; const prompt = require('prompt'); const exec = require('child_process').exec; const dryRun = process.env.DRY_RUN || false; const schema = { properties: { confirmation: { required: true, pattern: /^(y|n|yes|no)+$/ig, description: `You are about to release version ${version}, is that OK? (yes|no)` } } }; const pushTag = tag => { const cmd = `git push origin ${tag}`; exec(cmd, (error, stdout, stderr) => { console.log('Tag pushed to origin', tag); if (error !== null) { console.log(stderr); } }); }; const createTag = n => { const cmd = `git tag -a ${n} -m "Releasing version: ${n}"`; if (dryRun) { console.log('Pretending to create new tag', n); } else { exec(cmd, (error, stdout, stderr) => { console.log('New git tag created', n); pushTag(n); if (error !== null) { console.log(stderr); } }); } }; prompt.start(); prompt.get(schema, (err, result) => { if (err) { throw new Error(err); } const res = result.confirmation.toLowerCase(); if (res === 'y' || res === 'yes') { createTag(version); } else { console.log('Release aborted'); process.exit(0); } return true; }); ================================================ FILE: src/components/Alert.js ================================================ /** * Alert A facade for NSAlert. Includes a number of convenience methods and a * chainable interface. * * @param {Object} props Options for building the Alert and handling user input * @return {Object} alert */ // eslint-disable-next-line no-unused-vars const Alert = function(props) { // eslint-disable-next-line no-unused-vars const propTypes = { message: String, info: String, iconUrl: String, onConfirm: Function, onCancel: Function }; const alert = { views: [], el: NSAlert.alloc().init(), is: function(type) { return type === 'alert'; } }; const buttons = props.buttons || ['OK', 'Cancel']; alert.layout = function() { var containerHeight = 1; const container = NSView.alloc().initWithFrame( NSMakeRect(25, 100, 350, containerHeight) ); alert.views.forEach(function(view) { const el = view.el; const bounds = el.bounds(); bounds.origin.y = containerHeight; containerHeight += bounds.size.height + 8; el.setFrame(bounds); container.addSubview(el); }); const containerFrame = container.frame(); containerFrame.size.height = containerHeight; container.setFrame(containerFrame); alert.el.setAccessoryView(container); }; alert.runModal = function() { // Call layout before running opening the modal to account for any // dynamic layout alert.layout(); const onConfirm = props.onConfirm || function() {}; const onCancel = props.onCancel || function() {}; const res = parseInt(alert.el.runModal(), 10); const resHandler = (res === 1000) ? onConfirm : onCancel; resHandler(alert); return alert; }; /** * append Adds an element or elements to the Alert * * @param {Object|Array} newEl Either a single element or an array of elements. * @return {Object} alert */ alert.append = function(newEl) { const newViews = Array.isArray(newEl) ? newEl.reverse() : [newEl]; newViews.forEach(function(el) { alert.views.push(el); }); return alert; }; if (buttons.length) { for (var i = 0; i < buttons.length; i += 1) { alert.el.addButtonWithTitle(buttons[i]); } } if (props.message) { alert.el.setMessageText(props.message); } if (props.info) { alert.el.setInformativeText(props.info); } if (props.iconUrl) { alert.el.setIcon(NSImage.alloc().initByReferencingURL(props.iconUrl)); } return alert; }; ================================================ FILE: src/components/Label.js ================================================ /** * Label A facade for NSTextField without any editable styling. Includes a * number of convenience methods and a chainable interface. * * @param {Object} props Options for building the Label * @return {Object} label */ // eslint-disable-next-line no-unused-vars const Label = function(props) { // eslint-disable-next-line no-unused-vars const propTypes = { frame: Object, value: String }; /** * createLabel Internal method for creating new NSTextField * * @return {Object} NSTextField */ const createLabel = function(frame) { const f = frame || { x: 4, y: 100, width: 350, height: 16 }; const textField = NSTextField.alloc().initWithFrame( NSMakeRect(f.x, f.y, f.width, f.height)); textField.setDrawsBackground(false); textField.setEditable(false); textField.setBezeled(false); textField.setSelectable(true); return textField; }; const label = { el: createLabel(props.frame), is: function(type) { return type === 'label'; } }; /** * setStringValue Sets the string value for the label. * * @param {String} value * @return {Object} label */ label.setStringValue = function(value) { label.el.setStringValue(value); return label; }; if (props.value) { label.setStringValue(props.value); } return label; }; ================================================ FILE: src/components/PopUpButton.js ================================================ /** * PopUpButton A facade for NSPopUpButton. Includes a number of convenience * methods and a chainable interface. * * @param {Object} props Options for building the PopUpButton * @return {Object} popUpButton */ // eslint-disable-next-line no-unused-vars const PopUpButton = function(props) { // eslint-disable-next-line no-unused-vars const propTypes = { items: Array }; const popUpButton = { // NOTE: The documented signature is initWithFrame:pullsDown: but that // is undefined here. Think that's maybe a difference in CocoaScript? el: NSPopUpButton.alloc().initWithFrame( NSMakeRect(25, 100, 350, 28) ), is: function(type) { return type === 'select'; }, name: props.name || '', val: function() { return this.el.titleOfSelectedItem(); } }; if (props.items) { popUpButton.el.addItemsWithTitles(props.items); } return popUpButton; }; ================================================ FILE: src/components/TextField.js ================================================ /** * TextField A facade for NSTextField. Includes a number of convenience * methods and a chainable interface. * * @param {Object} props Options for building the TextField * @return {Object} textField */ // eslint-disable-next-line no-unused-vars const TextField = function(props) { // eslint-disable-next-line no-unused-vars const propTypes = { frame: Object, value: String }; /** * createTextField Internal method for creating new NSTextField * * @return {Object} NSTextField */ const createTextField = function(frame) { const f = frame || { x: 25, y: 100, width: 350, height: 24 }; return NSTextField.alloc().initWithFrame( NSMakeRect(f.x, f.y, f.width, f.height)); }; const textField = { el: createTextField(props.frame), is: function(type) { return type === 'input'; }, name: props.name || '', val: function() { return this.el.stringValue(); } }; /** * setStringValue Sets the string value for the textField. * * @param {String} value * @return {Object} textField */ textField.setStringValue = function(value) { textField.el.setStringValue(value); return textField; }; if (props.value) { textField.setStringValue(props.value); } return textField; }; ================================================ FILE: src/handlers/fillMurray.js ================================================ // eslint-disable-next-line no-unused-vars const fillMurray = createPluginHandler(function(props) { const GREY_TITLE = 'Black & white'; const initOpts = props.newImgFrame; const elements = [ new Label({ value: 'Width:' }), new TextField({ name: 'width', value: initOpts.width }), new Label({ value: 'Height:' }), new TextField({ name: 'height', value: initOpts.height }), new Label({ value: 'Type:' }), new PopUpButton({ name: 'type', items: ['Color', GREY_TITLE] }) ]; const onConfirm = createConfirmHandler({ api: props.api, group: props.target.group, host: 'fillmurray.com', initOpts: initOpts, urlBuilder: function(parts) { const base = `${parts.protocol}${parts.host}`; // Cast as a string because the value coming back is an object const type = (String(parts.allParts.type) === GREY_TITLE) ? '/g' : ''; return `${base}${type}/${parts.width}/${parts.height}`; } }); new Alert({ message: 'Fill Murray Options', info: 'Customize the wonderful Bill Murray image that will be created.', iconUrl: props.api.resourceNamed('fillmurray.icns'), onConfirm: onConfirm }).append(elements).runModal(); }); ================================================ FILE: src/handlers/placeCage.js ================================================ // eslint-disable-next-line no-unused-vars const placeCage = createPluginHandler(function(props) { const GREY_TITLE = 'Black & white'; const initOpts = props.newImgFrame; const elements = [ new Label({ value: 'Width:' }), new TextField({ name: 'width', value: initOpts.width }), new Label({ value: 'Height:' }), new TextField({ name: 'height', value: initOpts.height }), new Label({ value: 'Type:' }), new PopUpButton({ name: 'type', items: ['Color', GREY_TITLE] }) ]; const onConfirm = createConfirmHandler({ api: props.api, group: props.target.group, host: 'placecage.com', initOpts: initOpts, urlBuilder: function(parts) { const base = `${parts.protocol}${parts.host}`; // Cast as a string because the value coming back is an object const type = (String(parts.allParts.type) === GREY_TITLE) ? '/g' : ''; return `${base}${type}/${parts.width}/${parts.height}`; } }); new Alert({ message: 'Place Cage Options', info: 'Customize the image of the best actor of all time that will be created.', iconUrl: props.api.resourceNamed('placecage.icns'), onConfirm: onConfirm }).append(elements).runModal(); }); ================================================ FILE: src/handlers/placeHoldIt.js ================================================ // eslint-disable-next-line no-unused-vars const placeHoldIt = createPluginHandler(function(props) { const initOpts = props.newImgFrame; const elements = [ new Label({ value: 'Width:' }), new TextField({ name: 'width', value: initOpts.width }), new Label({ value: 'Height:' }), new TextField({ name: 'height', value: initOpts.height }), new Label({ value: 'Text:' }), new TextField({ name: 'text', value: 'placeholder' }), new Label({ value: 'Background color:' }), new TextField({ name: 'bgColor', value: 'aeaeae' }), new Label({ value: 'Text color:' }), new TextField({ name: 'color', value: '949494' }) ]; const onConfirm = createConfirmHandler({ api: props.api, group: props.target.group, host: 'placehold.it', initOpts: initOpts, urlBuilder: function(parts) { var url = `${parts.protocol}${parts.host}/${parts.width}x${parts.height}/${parts.allParts.bgColor}/${parts.allParts.color}`; const text = parts.allParts.text; if (text.length) { url += `?text=${encodeURIComponent(text)}`; } return url; } }); new Alert({ message: 'Placehold.it Options', info: 'Customize the image that will be created.', iconUrl: props.api.resourceNamed('placeholdit.icns'), onConfirm: onConfirm }).append(elements).runModal(); }); ================================================ FILE: src/handlers/placeKitten.js ================================================ // eslint-disable-next-line no-unused-vars const placeKitten = createPluginHandler(function(props) { const GREY_TITLE = 'Black & white'; const initOpts = props.newImgFrame; const elements = [ new Label({ value: 'Width:' }), new TextField({ name: 'width', value: initOpts.width }), new Label({ value: 'Height:' }), new TextField({ name: 'height', value: initOpts.height }), new Label({ value: 'Type:' }), new PopUpButton({ name: 'type', items: ['Color', GREY_TITLE] }) ]; const onConfirm = createConfirmHandler({ api: props.api, group: props.target.group, host: 'placekitten.com', initOpts: initOpts, urlBuilder: function(parts) { const base = `${parts.protocol}${parts.host}`; // Cast as a string because the value coming back is an object const type = (String(parts.allParts.type) === GREY_TITLE) ? '/g' : ''; return `${base}${type}/${parts.width}/${parts.height}`; } }); new Alert({ message: 'Place Kitten Options', info: 'Customize the kewl kitten image that will be created.', iconUrl: props.api.resourceNamed('placekitten.icns'), onConfirm: onConfirm }).append(elements).runModal(); }); ================================================ FILE: src/handlers/unsplashIt.js ================================================ // eslint-disable-next-line no-unused-vars const unsplashIt = createPluginHandler(function(props) { const GREY_TITLE = 'Black & white'; const BLUR_TITLE = 'Blurry'; const initOpts = props.newImgFrame; const elements = [ new Label({ value: 'Width:' }), new TextField({ name: 'width', value: initOpts.width }), new Label({ value: 'Height:' }), new TextField({ name: 'height', value: initOpts.height }), new Label({ value: 'Type:' }), new PopUpButton({ name: 'type', items: ['Color', GREY_TITLE] }), new Label({ value: 'Sharpness:' }), new PopUpButton({ name: 'blur', items: ['Focused', BLUR_TITLE] }) ]; const onConfirm = createConfirmHandler({ api: props.api, group: props.target.group, host: 'unsplash.it', initOpts: initOpts, urlBuilder: function(parts) { const base = `${parts.protocol}${parts.host}`; // Cast as a string because the value coming back is an object const type = (String(parts.allParts.type) === GREY_TITLE) ? '/g' : ''; const blur = (String(parts.allParts.blur) === BLUR_TITLE) ? '&blur' : ''; return `${base}${type}/${parts.width}/${parts.height}?random${blur}`; } }); new Alert({ message: 'Unsplash.it Options', info: 'Customize the image that will be created.', iconUrl: props.api.resourceNamed('unsplashit.icns'), onConfirm: onConfirm }).append(elements).runModal(); }); ================================================ FILE: src/index.cocoascript ================================================ @import './utils.js'; @import './components/Label.js'; @import './components/TextField.js'; @import './components/PopUpButton.js'; @import './components/Alert.js'; @import './handlers/fillMurray.js'; @import './handlers/placeCage.js'; @import './handlers/placeHoldIt.js'; @import './handlers/placeKitten.js'; @import './handlers/unsplashIt.js'; ================================================ FILE: src/utils.js ================================================ /** * createPluginHandler - Creates a handler function that takes the context * parameter required by Sketch plugins and enhances it with a number of * helpful properties. * * @param {Function} func * @return {Function} A function suitable to be used as a Plugin handler */ // eslint-disable-next-line no-unused-vars const createPluginHandler = function(func) { return function(context) { const api = context.api(); const document = api.selectedDocument; const selection = document.selectedLayers; const target = (selection.isEmpty) ? {group: document.selectedPage} : getTargetLayer(selection, context); const newImgFrame = (target.frame) ? { x: target.frame.x, y: target.frame.y, width: target.frame.width, height: target.frame.height } : { x: 0, y: 0, width: 400, height: 300 }; const props = { api: api, document: document, page: document.selectedPage, selection: selection, target: target, newImgFrame: newImgFrame }; func(props); }; }; /** * createConfirmHandler - Creates a standard Day Player alert confirmation handler * function that takes a single param, the Alert being used at the time. * * @param {Object} props Configuration for the handler * @param {Function} func Optional function to run after all standard bits * @return {Function} A function suitable to be used as an Alert.onConfirm */ // eslint-disable-next-line no-unused-vars const createConfirmHandler = function(props, func) { const urlBuilder = props.urlBuilder || function(parts) { return `${parts.protocol}${parts.host}/${parts.width}/${parts.height}`; }; return function(alert) { const userChosenOptions = alert.views.filter(function(view) { return view.is('input') || view.is('select'); }).reduce(function(obj, view, i) { obj[view.name] = view.val(); return obj; }, {}); const opts = Object.assign({}, props.initOpts, userChosenOptions); const sizeDisplay = `${opts.width}x${opts.height}`; props.api.message(`Creating a ${sizeDisplay}px image from ${props.host}...`); const url = urlBuilder({ protocol: 'https://', host: props.host, width: opts.width, height: opts.height, allParts: opts }); const img = props.group.newImage({ frame: new props.api.Rectangle(opts.x, opts.y, opts.width, opts.height), name: `${props.host}-${sizeDisplay}` }); img.imageURL = NSURL.URLWithString(url); }; }; /** * Determine if appending image to an artboard, group, layer, or none. * * @param {Selection} selection The current Selection * @param {Object} context The current context * @return {Object} target The selected layers */ const getTargetLayer = function(selection, context) { var layers = []; selection.iterate(function(layer) { layers.push(layer); }); // TODO: Currently we'll only create an image for a single layer selected. We // only grab the first one off the list of selections. In the future, we should // create an image for each selection. const firstLayer = layers[0]; var target = { selection: selection, frame: (firstLayer.isShape) ? firstLayer.frame : null }; if (firstLayer.isGroup) { target.group = firstLayer; } else { // FIXME: If the user has selected layer(s) that do not count as a group; // (shape, text, line, etc) we need to set the target.group to the parent // group of the selected layer(s). // The JS API does not currently offer a way to access the parentGroup of // a Layer object. To get around this, we use the low-level _object and // parentGroup() method. // Doing so gives us an unwrapped Sketch Object. We must use wrapObject to // ensure we return a wrapped object for the target.group. // In the future, it would be good to have a Layer.parentGroup getter. const api = context.api(); const document = api.selectedDocument; target.group = api.wrapObject(firstLayer._object.parentGroup(), document); } return target; }; /** * dumpObj - Introspect objects * @param {Object} obj */ // eslint-disable-next-line no-unused-vars const dumpObj = function(obj) { log('------------------------'); log('## Dumping object ' + obj); log('------------------------'); log('obj.properties:'); log(obj.class().mocha().properties()); log('obj.propertiesWithAncestors:'); log(obj.class().mocha().propertiesWithAncestors()); log('obj.classMethods:'); log(obj.class().mocha().classMethods()); log('obj.classMethodsWithAncestors:'); log(obj.class().mocha().classMethodsWithAncestors()); log('obj.instanceMethods:'); log(obj.class().mocha().instanceMethods()); log('obj.instanceMethodsWithAncestors:'); log(obj.class().mocha().instanceMethodsWithAncestors()); log('obj.protocols:'); log(obj.class().mocha().protocols()); log('obj.protocolsWithAncestors:'); log(obj.class().mocha().protocolsWithAncestors()); log('obj.treeAsDictionary():'); log(obj.treeAsDictionary()); };