├── .browserslistrc ├── src ├── core │ ├── ui │ │ ├── core-icon │ │ │ ├── entypo.woff │ │ │ ├── core-icon.html │ │ │ ├── core-icon.js │ │ │ ├── core-icon.less │ │ │ └── icons.js │ │ ├── core-menu │ │ │ ├── core-menu.html │ │ │ ├── core-menu.js │ │ │ └── core-menu.less │ │ ├── core-throbber │ │ │ ├── throbber.png │ │ │ ├── core-throbber.html │ │ │ ├── core-throbber.js │ │ │ └── core-throbber.less │ │ ├── core-label │ │ │ ├── core-label.html │ │ │ └── core-label.js │ │ ├── core-menu-separator │ │ │ ├── core-menu-separator.html │ │ │ ├── core-menu-separator.js │ │ │ └── core-menu-separator.less │ │ ├── core-extension │ │ │ ├── core-extension.html │ │ │ ├── core-extension.less │ │ │ └── core-extension.js │ │ ├── css │ │ │ ├── _common.less │ │ │ ├── _reset.less │ │ │ ├── _container.less │ │ │ ├── core.less │ │ │ ├── _layout.less │ │ │ └── _scrollbars.less │ │ ├── core-visible │ │ │ ├── core-visible.less │ │ │ └── core-visible.js │ │ ├── core-menu-item │ │ │ ├── core-menu-item.html │ │ │ ├── core-menu-item.less │ │ │ └── core-menu-item.js │ │ ├── core-slider │ │ │ ├── core-slider.js │ │ │ ├── core-slider.html │ │ │ └── core-slider.less │ │ ├── core-colorbox │ │ │ ├── core-colorbox.html │ │ │ ├── core-colorbox.less │ │ │ ├── host.jsx │ │ │ └── core-colorbox.js │ │ ├── core-button │ │ │ ├── core-button.html │ │ │ ├── _button-states.less │ │ │ ├── core-button.js │ │ │ └── core-button.less │ │ ├── core-textbox │ │ │ ├── core-textbox.html │ │ │ ├── core-textbox.js │ │ │ └── core-textbox.less │ │ ├── core-numberbox │ │ │ ├── core-numberbox.html │ │ │ ├── core-numberbox.less │ │ │ └── core-numberbox.js │ │ ├── core-toolbar │ │ │ ├── core-toolbar.html │ │ │ ├── core-toolbar.less │ │ │ └── core-toolbar.js │ │ ├── core-icon-button │ │ │ ├── core-icon-button.html │ │ │ ├── core-icon-button.less │ │ │ └── core-icon-button.js │ │ ├── core-field │ │ │ ├── core-field.less │ │ │ └── core-field.js │ │ ├── core-menu-button │ │ │ ├── core-menu-button.html │ │ │ ├── core-menu-button.less │ │ │ └── core-menu-button.js │ │ ├── core-checkbox │ │ │ ├── core-checkbox.html │ │ │ ├── core-checkbox.less │ │ │ └── core-checkbox.js │ │ ├── core-base │ │ │ └── core-base.js │ │ ├── core-tooltip │ │ │ ├── core-tooltip.less │ │ │ └── core-tooltip.js │ │ ├── core-dropdown │ │ │ ├── core-dropdown.html │ │ │ ├── core-dropdown.less │ │ │ └── core-dropdown.js │ │ └── core-combobox │ │ │ ├── core-combobox.less │ │ │ ├── core-combobox.html │ │ │ └── core-combobox.js │ ├── assert.js │ ├── polyfills.js │ ├── SlowTask.js │ ├── mixins.js │ ├── index.js │ ├── Theme.js │ ├── Extension.js │ ├── Settings.js │ └── Logger.js ├── photoshop │ ├── host │ │ ├── getOpenDocumentIDs.jsx │ │ ├── callGenerator.jsx │ │ ├── copyToClipboard.jsx │ │ ├── getLayerShape.jsx │ │ ├── getDocumentInfo.jsx │ │ ├── getDocumentPath.jsx │ │ ├── getPhotoshopExecutableLocation.jsx │ │ ├── networkEventSubscribe.jsx │ │ └── getLayerPixmap.jsx │ ├── Deferred.js │ ├── ui │ │ ├── photoshop-panel │ │ │ ├── photoshop-panel.less │ │ │ └── photoshop-panel.html │ │ └── photoshop-document │ │ │ └── host │ │ │ ├── setXMPMetadata.jsx │ │ │ └── getXMPMetadata.jsx │ ├── Pixmap.js │ ├── ExportTarget.js │ └── PhotoshopCrypto.js ├── exporter-main │ ├── DefaultSettings.json │ ├── exporter-panel │ │ ├── exporter-panel.html │ │ └── exporter-panel.js │ ├── exporter-document │ │ └── exporter-document.html │ ├── exporter-target │ │ ├── exporter-target.less │ │ ├── exporter-target.js │ │ └── exporter-target.html │ └── index.js └── exporter-settings │ ├── index.js │ └── settings-panel │ ├── settings-panel.js │ ├── settings-panel.less │ └── settings-panel.html ├── bundle ├── exporter │ ├── icons │ │ ├── icon-dark.png │ │ ├── icon-light.png │ │ ├── icon-dark-hover.png │ │ ├── icon-light-hover.png │ │ ├── icon-dark-disabled.png │ │ └── icon-light-disabled.png │ ├── main.html │ └── settings.html ├── manifest.mxi.xml └── scripts │ ├── Expresso Export All.jsx │ └── Expresso Export Enabled.jsx ├── .babelrc ├── .vscode ├── settings.json └── launch.json ├── jsconfig.json ├── .postcssrc.json ├── scripts ├── webpack.loader.ractive.js ├── webpack.loader.jsx.js ├── webpack.config.js ├── cepy.exporter.config.js └── tasks │ └── exporter.js ├── .gitattributes ├── bin └── build.js ├── .eslintrc.json ├── .gitignore ├── package.json ├── readme.md └── changelog.md /.browserslistrc: -------------------------------------------------------------------------------- 1 | Chrome >= 27 2 | -------------------------------------------------------------------------------- /src/core/ui/core-icon/entypo.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcamarlinghi/expresso/HEAD/src/core/ui/core-icon/entypo.woff -------------------------------------------------------------------------------- /bundle/exporter/icons/icon-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcamarlinghi/expresso/HEAD/bundle/exporter/icons/icon-dark.png -------------------------------------------------------------------------------- /bundle/exporter/icons/icon-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcamarlinghi/expresso/HEAD/bundle/exporter/icons/icon-light.png -------------------------------------------------------------------------------- /src/core/ui/core-menu/core-menu.html: -------------------------------------------------------------------------------- 1 |
{{yield}}
2 | -------------------------------------------------------------------------------- /src/core/ui/core-throbber/throbber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcamarlinghi/expresso/HEAD/src/core/ui/core-throbber/throbber.png -------------------------------------------------------------------------------- /bundle/exporter/icons/icon-dark-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcamarlinghi/expresso/HEAD/bundle/exporter/icons/icon-dark-hover.png -------------------------------------------------------------------------------- /bundle/exporter/icons/icon-light-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcamarlinghi/expresso/HEAD/bundle/exporter/icons/icon-light-hover.png -------------------------------------------------------------------------------- /src/core/ui/core-label/core-label.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bundle/exporter/icons/icon-dark-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcamarlinghi/expresso/HEAD/bundle/exporter/icons/icon-dark-disabled.png -------------------------------------------------------------------------------- /bundle/exporter/icons/icon-light-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcamarlinghi/expresso/HEAD/bundle/exporter/icons/icon-light-disabled.png -------------------------------------------------------------------------------- /src/core/ui/core-menu-separator/core-menu-separator.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /src/core/ui/core-extension/core-extension.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{yield}} 4 |
5 | -------------------------------------------------------------------------------- /src/core/ui/core-icon/core-icon.html: -------------------------------------------------------------------------------- 1 | {{ icon && icon.length ? icons()[icon] : "" }} 2 | -------------------------------------------------------------------------------- /src/core/ui/css/_common.less: -------------------------------------------------------------------------------- 1 | 2 | // Common rules 3 | .box-sizing() { 4 | box-sizing: border-box; 5 | } 6 | 7 | .unselectable() { 8 | -webkit-user-select: none; 9 | } 10 | -------------------------------------------------------------------------------- /src/core/ui/core-visible/core-visible.less: -------------------------------------------------------------------------------- 1 | @import "../css/_variables.less"; 2 | @import "../css/_common.less"; 3 | .theme(common); 4 | 5 | .core-hidden { 6 | display: none !important; 7 | } 8 | -------------------------------------------------------------------------------- /src/photoshop/host/getOpenDocumentIDs.jsx: -------------------------------------------------------------------------------- 1 | var i = 0, 2 | l = app.documents.length, 3 | ids = []; 4 | 5 | for (; i < l; i++) 6 | { 7 | ids.push(app.documents[i].id); 8 | } 9 | 10 | ids.join(':'); 11 | -------------------------------------------------------------------------------- /src/core/ui/core-menu-item/core-menu-item.html: -------------------------------------------------------------------------------- 1 |
{{yield}}
2 | -------------------------------------------------------------------------------- /src/exporter-main/DefaultSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "useLowerCaseExtension": true, 3 | "useLowerCaseFileNames": false, 4 | "saveOriginalAfterExport": false, 5 | "useRelativePaths": true, 6 | "enableTGACompression": false 7 | } 8 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/env", { "targets": { "chrome": "27" } }] 4 | ], 5 | "plugins": [ 6 | "@babel/transform-strict-mode", 7 | ["@babel/plugin-transform-for-of", { "loose": true }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /src/core/ui/core-slider/core-slider.js: -------------------------------------------------------------------------------- 1 | 2 | import CoreNumberBox from '../core-numberbox/core-numberbox.js'; 3 | import './core-slider.less'; 4 | 5 | export default CoreNumberBox.extend({ 6 | 7 | template: require('./core-slider.html'), 8 | 9 | }); 10 | -------------------------------------------------------------------------------- /src/photoshop/host/callGenerator.jsx: -------------------------------------------------------------------------------- 1 | var desc = new ActionDescriptor(); 2 | desc.putString(stringIDToTypeID('name'), params.menu); 3 | desc.putString(stringIDToTypeID('data'), params.data); 4 | executeAction(stringIDToTypeID('generateAssets'), desc, DialogModes.NO); 5 | -------------------------------------------------------------------------------- /src/core/ui/core-colorbox/core-colorbox.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /src/core/ui/core-button/core-button.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "eslint.validate": [ 4 | "javascript" 5 | ], 6 | "files.associations": { 7 | "*.jsx": "javascript" 8 | }, 9 | "files.exclude": { 10 | "**/build": true, 11 | "**/release": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/photoshop/host/copyToClipboard.jsx: -------------------------------------------------------------------------------- 1 | var ktextToClipboardStr = stringIDToTypeID('textToClipboard'), 2 | keyTextData = charIDToTypeID('TxtD'), 3 | testStrDesc = new ActionDescriptor(); 4 | testStrDesc.putString(keyTextData, params.clipboard); 5 | executeAction(ktextToClipboardStr, testStrDesc, DialogModes.NO); 6 | -------------------------------------------------------------------------------- /src/core/ui/core-slider/core-slider.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/core/ui/core-textbox/core-textbox.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/core/ui/core-numberbox/core-numberbox.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/core/ui/core-toolbar/core-toolbar.html: -------------------------------------------------------------------------------- 1 | {{#vertical}} 2 |
{{yield}}
3 | {{/}} 4 | {{^vertical}} 5 |
{{yield}}
6 | {{/}} 7 | -------------------------------------------------------------------------------- /src/core/assert.js: -------------------------------------------------------------------------------- 1 | 2 | export function assert(condition, message) 3 | { 4 | if (!RELEASE && !condition) 5 | { 6 | throw new Error(message); 7 | } 8 | } 9 | 10 | export function assertType(condition, message) 11 | { 12 | if (!RELEASE && !condition) 13 | { 14 | throw new TypeError(message); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/photoshop/host/getLayerShape.jsx: -------------------------------------------------------------------------------- 1 | var actionDescriptor = new ActionDescriptor(); 2 | 3 | actionDescriptor.putInteger(stringIDToTypeID('documentID'), params.documentId); 4 | actionDescriptor.putInteger(stringIDToTypeID('layerID'), params.layerId); 5 | 6 | executeAction(stringIDToTypeID('sendLayerShapeToNetworkClient'), actionDescriptor, DialogModes.NO); 7 | -------------------------------------------------------------------------------- /src/core/ui/core-icon-button/core-icon-button.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/core/ui/core-visible/core-visible.js: -------------------------------------------------------------------------------- 1 | 2 | import './core-visible.less'; 3 | 4 | export default function (node, content) 5 | { 6 | node.classList.toggle('core-hidden', !Boolean.toBoolean(content)); 7 | 8 | return { 9 | teardown: function () 10 | { 11 | node.classList.remove('core-hidden'); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/core/ui/core-field/core-field.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | .core-field { 7 | .unselectable; 8 | .box-sizing; 9 | margin: 0 @baseMargin @baseMargin 0; 10 | display: inline-block; 11 | vertical-align: middle; 12 | outline: none; 13 | border-radius: 0; 14 | } 15 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "baseUrl": "./src/", 5 | "paths": { 6 | "core/*": ["core/*"], 7 | "photoshop/*": ["photoshop/*"], 8 | "exporter-main/*": ["exporter-main/*"], 9 | "exporter-settings/*": ["exporter-settings/*"], 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.postcssrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "cssnano": { 4 | "preset": [ 5 | "advanced", 6 | { 7 | "autoprefixer": { 8 | "add": true 9 | }, 10 | "reduceIdents": true 11 | } 12 | ] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/core/ui/core-throbber/core-throbber.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
{{message}}
5 |
6 | -------------------------------------------------------------------------------- /src/core/ui/core-extension/core-extension.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | .core-extension { 7 | height: 100%; 8 | padding: 0; 9 | margin: 0; 10 | } 11 | 12 | .core-extension > .core-container { 13 | padding: 0; 14 | border-bottom: 0; 15 | } 16 | 17 | .core-extension *:last-child { 18 | margin-right: 0; 19 | } 20 | -------------------------------------------------------------------------------- /src/core/ui/core-label/core-label.js: -------------------------------------------------------------------------------- 1 | 2 | import CoreBase from '../core-base/core-base.js'; 3 | 4 | export default CoreBase.extend({ 5 | 6 | template: require('./core-label.html'), 7 | 8 | data: { 9 | 10 | /** 11 | * If true, the label is rendered greyed-out. 12 | * @type Boolean 13 | * @default false 14 | */ 15 | disabled: false, 16 | 17 | }, 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /scripts/webpack.loader.ractive.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Webpack loader for Ractive components. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | const Ractive = require('ractive'), 9 | htmlclean = require('htmlclean'); 10 | 11 | const load = function (content) 12 | { 13 | const parsed = Ractive.parse(htmlclean(content)); 14 | this.cacheable(); 15 | return 'module.exports = ' + JSON.stringify(parsed) + ';'; 16 | }; 17 | 18 | module.exports = load; 19 | -------------------------------------------------------------------------------- /bundle/exporter/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /bundle/exporter/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/core/ui/core-menu-separator/core-menu-separator.js: -------------------------------------------------------------------------------- 1 | 2 | import CoreBase from '../core-base/core-base.js'; 3 | import './core-menu-separator.less'; 4 | 5 | export default CoreBase.extend({ 6 | 7 | template: require('./core-menu-separator.html'), 8 | 9 | data: { 10 | 11 | /** 12 | * Whether this separator is visible to user. 13 | * @type Boolean 14 | * @default true 15 | */ 16 | visible: true, 17 | 18 | }, 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /src/core/ui/core-menu/core-menu.js: -------------------------------------------------------------------------------- 1 | 2 | import CoreBase from '../core-base/core-base.js'; 3 | import './core-menu.less'; 4 | 5 | export default CoreBase.extend({ 6 | 7 | template: require('./core-menu.html'), 8 | 9 | twoway: true, 10 | 11 | lazy: true, 12 | 13 | data: { 14 | 15 | /** 16 | * Whether the menu is visible. 17 | * @type Boolean 18 | * @default true 19 | */ 20 | visible: true, 21 | 22 | }, 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /src/core/ui/core-menu-button/core-menu-button.html: -------------------------------------------------------------------------------- 1 | 7 | {{yield}} 8 | -------------------------------------------------------------------------------- /src/exporter-main/exporter-panel/exporter-panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/core/ui/core-checkbox/core-checkbox.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /src/photoshop/host/getDocumentInfo.jsx: -------------------------------------------------------------------------------- 1 | var idNS = stringIDToTypeID('sendDocumentInfoToNetworkClient'), 2 | desc = new ActionDescriptor(); 3 | desc.putString(stringIDToTypeID('version'), '1.3.0'); 4 | 5 | for (var key in params.flags) 6 | { 7 | if (params.flags.hasOwnProperty(key)) 8 | { 9 | desc.putBoolean(stringIDToTypeID(key), params.flags[key]); 10 | } 11 | } 12 | 13 | if (params.documentId) 14 | { 15 | desc.putInteger(stringIDToTypeID('documentID'), params.documentId); 16 | } 17 | 18 | executeAction(idNS, desc, DialogModes.NO); 19 | -------------------------------------------------------------------------------- /src/core/ui/core-base/core-base.js: -------------------------------------------------------------------------------- 1 | 2 | import Ractive from 'ractive'; 3 | 4 | export default Ractive.extend({ 5 | 6 | lazy: true, 7 | 8 | twoway: false, 9 | 10 | isolated: true, 11 | 12 | data: { 13 | 14 | /** 15 | * Class names. 16 | * @type String 17 | * @default null 18 | */ 19 | css: null, 20 | 21 | /** 22 | * Component instance style. 23 | * @type String 24 | * @default null 25 | */ 26 | style: null, 27 | 28 | }, 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /src/photoshop/host/getDocumentPath.jsx: -------------------------------------------------------------------------------- 1 | 2 | var documentPath = ''; 3 | 4 | try 5 | { 6 | if (params.documentId) 7 | { 8 | for (var i = 0, l = app.documents.length; i < l; i++) 9 | { 10 | if (app.documents[i].id === params.documentId) 11 | { 12 | documentPath = app.documents[i].fullName.fsName; 13 | break; 14 | } 15 | } 16 | } 17 | else 18 | { 19 | documentPath = app.activeDocument.fullName.fsName; 20 | } 21 | } 22 | catch (e) { } 23 | 24 | documentPath; 25 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /src/core/ui/core-tooltip/core-tooltip.less: -------------------------------------------------------------------------------- 1 | @import "../css/_variables.less"; 2 | @import "../css/_common.less"; 3 | .theme(common); 4 | 5 | .core-tooltip { 6 | .box-sizing(); 7 | .unselectable(); 8 | padding: @basePadding / 2 @basePadding; 9 | border: @baseBorderSize solid #000; 10 | display: none; 11 | background: #ffffcc; 12 | font: @baseFont; 13 | font-size: 10px; 14 | letter-spacing: 0; 15 | text-shadow: none; 16 | cursor: default; 17 | color: #000; 18 | position: fixed; 19 | z-index: 1000; 20 | //white-space: nowrap; 21 | } 22 | -------------------------------------------------------------------------------- /src/core/ui/core-menu-button/core-menu-button.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | .core-menu-button > .core-icon.menu-icon { 7 | position: absolute; 8 | right: 2px; 9 | bottom: 0px; 10 | font-size: 8px; 11 | } 12 | 13 | .menu-button(@theme) { 14 | .theme(@theme); 15 | 16 | .@{theme} .core-menu-button > .core-icon.menu-icon { 17 | color: darken(@textColor, 20%); 18 | } 19 | } 20 | 21 | .menu-button(dark); 22 | .menu-button(medium-dark); 23 | .menu-button(light); 24 | .menu-button(medium-light); 25 | -------------------------------------------------------------------------------- /src/core/ui/core-menu-item/core-menu-item.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | .core-menu-item { 7 | .box-sizing; 8 | .unselectable; 9 | 10 | &[data-visible=false] { 11 | display: none; 12 | } 13 | } 14 | 15 | .core-menu-item(@theme) { 16 | .theme(@theme); 17 | 18 | .@{theme} .core-menu-item { 19 | &[data-disabled=true] { 20 | color: @textDisabledColor; 21 | } 22 | } 23 | } 24 | 25 | .core-menu-item(dark); 26 | .core-menu-item(medium-dark); 27 | .core-menu-item(light); 28 | .core-menu-item(medium-light); 29 | -------------------------------------------------------------------------------- /scripts/webpack.loader.jsx.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Webpack loader for Extendscript files. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | const yui = require('yuicompressor'); 9 | 10 | const load = function (content) 11 | { 12 | const callback = this.async(); 13 | this.cacheable(); 14 | 15 | yui.compress( 16 | content, 17 | { 18 | charset: 'utf8', 19 | type: 'js', 20 | nomunge: true, 21 | 'preserve-semi': true 22 | }, 23 | (error, data, extra) => callback(error, 'module.exports = ' + JSON.stringify(data) + ';') 24 | ); 25 | }; 26 | 27 | module.exports = load; 28 | -------------------------------------------------------------------------------- /src/core/ui/core-button/_button-states.less: -------------------------------------------------------------------------------- 1 | 2 | // Button states 3 | .button-up() { 4 | color: @btnTextColor; 5 | background-color: @btnBackground; 6 | border: @baseBorderSize solid @btnBorderColor; 7 | } 8 | 9 | .button-hover() { 10 | color: @btnTextColor; 11 | background: @btnBackground; 12 | border: @baseBorderSize solid @outlineColor; 13 | } 14 | 15 | .button-down() { 16 | color: @btnTextColor; 17 | background: @btnBackgroundActive; 18 | border: @baseBorderSize solid @btnBorderColor; 19 | } 20 | 21 | .button-disabled() { 22 | color: @textDisabledColor; 23 | background: @btnBackgroundDisabled; 24 | } 25 | -------------------------------------------------------------------------------- /src/exporter-main/exporter-document/exporter-document.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | {{#targets:targetIndex}} 7 | 8 | {{/targets}} 9 |
10 | -------------------------------------------------------------------------------- /bin/build.js: -------------------------------------------------------------------------------- 1 | //#!/usr/bin/env node 2 | 3 | /* eslint-disable no-console */ 4 | 'use strict'; 5 | 6 | if (process.argv.length < 4) 7 | { 8 | console.log('Expresso Build Tool.\n\nUsage: build.js '); 9 | } 10 | else 11 | { 12 | const taskName = process.argv[2], 13 | mode = process.argv[3]; 14 | 15 | // Find target task file 16 | const task = require(`../scripts/tasks/${taskName}.js`); 17 | 18 | // Enable verbose output 19 | process.env.DEBUG = 'cepy'; 20 | 21 | // And run it with the specified mode 22 | task.run(mode) 23 | .then(process.exit) 24 | .catch(err => 25 | { 26 | console.error(err); 27 | process.exit(1); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /bundle/manifest.mxi.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% productList.forEach(function (product) { %} 8 | {%= product %} 9 | {% }); %} 10 | 11 | 12 | {% fileList.forEach(function (file) { %} 13 | {%= file %} 14 | {% }); %} 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /bundle/scripts/Expresso Export All.jsx: -------------------------------------------------------------------------------- 1 | #target photoshop 2 | try 3 | { 4 | app.bringToFront(); 5 | 6 | // Load PlugPlug 7 | if (typeof ExternalObject.PlugPlugExternalObject !== 'object') 8 | { 9 | ExternalObject.PlugPlugExternalObject = new ExternalObject('lib:PlugPlugExternalObject'); 10 | } 11 | 12 | // Trigger event 13 | const eventObj = new CSXSEvent(); 14 | eventObj.type = 'com.expresso.exporter.exportAll'; 15 | eventObj.data = ''; 16 | eventObj.dispatch(); 17 | } 18 | catch (error) {} 19 | finally 20 | { 21 | // Unload PlugPlug 22 | if (typeof ExternalObject.PlugPlugExternalObject === 'object') 23 | { 24 | ExternalObject.PlugPlugExternalObject = undefined; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bundle/scripts/Expresso Export Enabled.jsx: -------------------------------------------------------------------------------- 1 | #target photoshop 2 | try 3 | { 4 | app.bringToFront(); 5 | 6 | // Load PlugPlug 7 | if (typeof ExternalObject.PlugPlugExternalObject !== 'object') 8 | { 9 | ExternalObject.PlugPlugExternalObject = new ExternalObject('lib:PlugPlugExternalObject'); 10 | } 11 | 12 | // Trigger event 13 | const eventObj = new CSXSEvent(); 14 | eventObj.type = 'com.expresso.exporter.exportEnabled'; 15 | eventObj.data = ''; 16 | eventObj.dispatch(); 17 | } 18 | catch (error) {} 19 | finally 20 | { 21 | // Unload PlugPlug 22 | if (typeof ExternalObject.PlugPlugExternalObject === 'object') 23 | { 24 | ExternalObject.PlugPlugExternalObject = undefined; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/core/ui/core-extension/core-extension.js: -------------------------------------------------------------------------------- 1 | 2 | import CoreBase from '../core-base/core-base.js'; 3 | import Extension from '../../Extension.js'; 4 | import './core-extension.less'; 5 | 6 | export default CoreBase.extend({ 7 | 8 | template: require('./core-extension.html'), 9 | 10 | onconstruct() 11 | { 12 | this._super(); 13 | this.extension = Extension.get(); 14 | 15 | this.extension.on('busy', (details) => 16 | { 17 | const throbber = this.findComponent('core-throbber'); 18 | if (throbber) 19 | { 20 | throbber.set('visible', details.status); 21 | throbber.set('message', details.task); 22 | } 23 | }); 24 | } 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /src/exporter-settings/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * Main Exporter Settings entry point. 5 | * 6 | */ 7 | 8 | import 'core'; 9 | import Extension from 'core/Extension.js'; 10 | import ExporterDefaultSettings from '../exporter-main/DefaultSettings.json'; 11 | import SettingsPanel from './settings-panel/settings-panel.js'; 12 | 13 | const extension = Extension.create({ 14 | name: 'Exporter Settings', 15 | vendor: 'Expresso', 16 | version: VERSION, 17 | folder: 'Expresso/Exporter', 18 | log: { filename: 'exporter-settings.log' }, 19 | settings: { 20 | defaults: ExporterDefaultSettings, 21 | }, 22 | }); 23 | 24 | // Create the UI 25 | Object.defineProperty(extension, 'ui', { value: new SettingsPanel(), enumerable: true }); 26 | -------------------------------------------------------------------------------- /src/photoshop/host/getPhotoshopExecutableLocation.jsx: -------------------------------------------------------------------------------- 1 | var classProperty = charIDToTypeID('Prpr'); 2 | var classApplication = charIDToTypeID('capp'); 3 | var typeOrdinal = charIDToTypeID('Ordn'); 4 | var enumTarget = charIDToTypeID('Trgt'); 5 | var kexecutablePathStr = stringIDToTypeID('executablePath'); 6 | var typeNULL = charIDToTypeID('null'); 7 | var actionGet = charIDToTypeID('getd'); 8 | 9 | var desc1 = new ActionDescriptor(); 10 | var ref1 = new ActionReference(); 11 | ref1.putProperty(classProperty, kexecutablePathStr); 12 | ref1.putEnumerated(classApplication, typeOrdinal, enumTarget); 13 | desc1.putReference(typeNULL, ref1); 14 | var result = executeAction(actionGet, desc1, DialogModes.NO); 15 | String((new File(result.getPath(kexecutablePathStr))).fsName); 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "globals": { 4 | "RELEASE": false, 5 | "VERSION": false, 6 | "WEBSITE": false, 7 | "CSInterface": false, 8 | "CSEvent": false 9 | }, 10 | "env": { 11 | "browser": true, 12 | "commonjs": true, 13 | "es6": true, 14 | "node": true 15 | }, 16 | "parserOptions": { 17 | "ecmaVersion": 6, 18 | "sourceType": "module", 19 | "ecmaFeatures": { 20 | "impliedStrict": true 21 | } 22 | }, 23 | "rules": { 24 | "no-empty": "warn", 25 | "no-undef": "warn", 26 | "no-unreachable": "warn", 27 | "no-unused-vars": "warn", 28 | "valid-typeof": "warn" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/core/ui/core-menu-separator/core-menu-separator.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | .core-menu-separator { 7 | .box-sizing; 8 | .unselectable; 9 | min-height: 4px !important; 10 | max-height: 4px !important; 11 | margin: 4px 0 2px 0 !important; 12 | padding: 0 !important; 13 | 14 | &[data-visible=false] { 15 | display: none; 16 | } 17 | } 18 | 19 | .core-menu-separator(@theme) { 20 | .theme(@theme); 21 | 22 | .@{theme} .core-menu-separator { 23 | border-top: 1px solid @baseBorderColor; 24 | } 25 | } 26 | 27 | .core-menu-separator(dark); 28 | .core-menu-separator(medium-dark); 29 | .core-menu-separator(light); 30 | .core-menu-separator(medium-light); 31 | -------------------------------------------------------------------------------- /src/photoshop/Deferred.js: -------------------------------------------------------------------------------- 1 | 2 | const noop = function () {}; 3 | 4 | /** 5 | * Barebone Deferred implementation. 6 | */ 7 | export default class Deferred 8 | { 9 | constructor() 10 | { 11 | let _progressed = noop; 12 | 13 | this.resolve = null; 14 | this.reject = null; 15 | this.promise = new Promise((resolve, reject) => 16 | { 17 | this.resolve = resolve; 18 | this.reject = reject; 19 | }); 20 | this.progressed = (func) => { _progressed = (typeof func === 'function') ? func : noop; return this.promise; }; 21 | this.notify = (value) => 22 | { 23 | if (_progressed) 24 | { 25 | _progressed(value); 26 | } 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/core/ui/core-icon/core-icon.js: -------------------------------------------------------------------------------- 1 | 2 | import CoreBase from '../core-base/core-base.js'; 3 | import './core-icon.less'; 4 | import icons from './icons.js'; 5 | 6 | export default CoreBase.extend({ 7 | 8 | template: require('./core-icon.html'), 9 | 10 | data: { 11 | 12 | /** 13 | * Tooltip for this icon. 14 | * @type String 15 | * @default null 16 | */ 17 | tooltip: null, 18 | 19 | /** 20 | * The icon. 21 | * @type String 22 | * @default '' 23 | */ 24 | icon: '', 25 | 26 | /** 27 | * Icons dictionary. 28 | * @type Object 29 | */ 30 | icons: function () 31 | { 32 | return icons; 33 | }, 34 | 35 | }, 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ========================= 2 | # Visual Studio 3 | # ========================= 4 | *.sln 5 | *.suo 6 | .vs 7 | 8 | # ========================= 9 | # Windows detritus 10 | # ========================= 11 | 12 | # Windows image file caches 13 | Thumbs.db 14 | ehthumbs.db 15 | 16 | # Folder config file 17 | Desktop.ini 18 | 19 | # Recycle Bin used on file shares 20 | $RECYCLE.BIN/ 21 | 22 | # ========================= 23 | # Node JS 24 | # ========================= 25 | 26 | lib-cov 27 | lcov.info 28 | *.seed 29 | *.log 30 | *.csv 31 | *.dat 32 | *.out 33 | *.pid 34 | *.gz 35 | 36 | pids 37 | logs 38 | results 39 | #build 40 | .grunt 41 | 42 | node_modules 43 | 44 | # ========================= 45 | # Expresso 46 | # ========================= 47 | 48 | build 49 | release 50 | scripts/distrib 51 | .staging 52 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "chrome", 6 | "request": "attach", 7 | "name": "Exporter Main", 8 | "port": 8000, 9 | "webRoot": "${workspaceRoot}/build/com.expresso.exporter/exporter/main.html", 10 | "sourceMapPathOverrides": { 11 | "webpack:///*": "${workspaceRoot}/*" 12 | } 13 | }, 14 | { 15 | "type": "chrome", 16 | "request": "attach", 17 | "name": "Exporter Settings", 18 | "port": 8100, 19 | "webRoot": "${workspaceRoot}/build/com.expresso.exporter/exporter/settings.html", 20 | "sourceMapPathOverrides": { 21 | "webpack:///*": "${workspaceRoot}/*" 22 | } 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/core/ui/core-colorbox/core-colorbox.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | .core-colorbox { 7 | padding: 0; 8 | min-height: 20px; 9 | height: 20px; 10 | min-width: 20px; 11 | width: 20px; 12 | 13 | &:focus { 14 | outline: none; 15 | } 16 | } 17 | 18 | .colorbox(@theme) { 19 | .@{theme} { 20 | .theme(@theme); 21 | 22 | .core-colorbox { 23 | border: @baseBorderSize solid @btnBorderColor; 24 | 25 | &:disabled { 26 | filter: grayscale(1); 27 | //background: @baseBorderColorLight; 28 | border: @baseBorderSize solid @baseBorderColorLight; 29 | } 30 | } 31 | } 32 | } 33 | 34 | .colorbox(dark); 35 | .colorbox(medium-dark); 36 | .colorbox(light); 37 | .colorbox(medium-light); 38 | -------------------------------------------------------------------------------- /src/core/ui/core-toolbar/core-toolbar.less: -------------------------------------------------------------------------------- 1 | @import "../css/_variables.less"; 2 | @import "../css/_common.less"; 3 | .theme(common); 4 | 5 | .core-toolbar { 6 | .box-sizing; 7 | .unselectable; 8 | display: block; 9 | min-height: 25px !important; 10 | max-height: 25px !important; 11 | width: 100%; 12 | padding-right: 20px; 13 | 14 | &[data-vertical=true] { 15 | min-width: 32px; 16 | width: inherit; 17 | max-width: 32px; 18 | min-height: inherit; 19 | height: 100%; 20 | max-height: inherit; 21 | } 22 | } 23 | 24 | .toolbar(@theme) { 25 | .@{theme} .core-toolbar { 26 | .theme(@theme); 27 | background: @bodyBackground; 28 | border-top: 1px solid @baseBorderColorLight; 29 | } 30 | } 31 | 32 | .toolbar(dark); 33 | .toolbar(medium-dark); 34 | .toolbar(light); 35 | .toolbar(medium-light); 36 | -------------------------------------------------------------------------------- /src/core/polyfills.js: -------------------------------------------------------------------------------- 1 | 2 | // String.startsWith 3 | // Needed to support Photoshop CC 2015 4 | // Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith 5 | if (!String.prototype.startsWith) 6 | { 7 | String.prototype.startsWith = function (search, pos) 8 | { 9 | return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; 10 | }; 11 | } 12 | 13 | // String.endsWith 14 | // Needed to support Photoshop CC 2015 15 | // Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith 16 | if (!String.prototype.endsWith) 17 | { 18 | String.prototype.endsWith = function (search, this_len) 19 | { 20 | if (this_len === undefined || this_len > this.length) 21 | { 22 | this_len = this.length; 23 | } 24 | return this.substring(this_len - search.length, this_len) === search; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/core/ui/core-colorbox/host.jsx: -------------------------------------------------------------------------------- 1 | 2 | var newColor = null, 3 | baseColor = params.baseColor; 4 | 5 | try 6 | { 7 | if (typeof baseColor === 'string' && baseColor.length === 7) 8 | { 9 | // Remove trailing hash 10 | baseColor = baseColor.substring(1); 11 | } 12 | else 13 | { 14 | baseColor = '666666'; 15 | } 16 | 17 | // Save old foreground color 18 | const oldColor = app.foregroundColor.rgb.hexValue; 19 | 20 | // Set base color as foreground color 21 | app.foregroundColor.rgb.hexValue = baseColor; 22 | 23 | if (app.showColorPicker()) 24 | { 25 | newColor = app.foregroundColor.rgb.hexValue; 26 | } 27 | else 28 | { 29 | newColor = baseColor; 30 | } 31 | 32 | // Reset old foreground color 33 | app.foregroundColor.rgb.hexValue = oldColor; 34 | 35 | // Return color 36 | '#' + newColor; 37 | } 38 | catch (e) 39 | { 40 | 'Error while selecting color: ' + e; 41 | } 42 | -------------------------------------------------------------------------------- /src/core/ui/core-toolbar/core-toolbar.js: -------------------------------------------------------------------------------- 1 | 2 | import CoreBase from '../core-base/core-base.js'; 3 | import './core-toolbar.less'; 4 | 5 | export default CoreBase.extend({ 6 | 7 | template: require('./core-toolbar.html'), 8 | 9 | data: { 10 | 11 | /** 12 | * Whether the toolbar should develop vertically. 13 | * @type Boolean 14 | * @default false 15 | */ 16 | vertical: false, 17 | 18 | }, 19 | 20 | onrender: function () 21 | { 22 | this._super(); 23 | this.setupButtons(); 24 | }, 25 | 26 | setupButtons: function () 27 | { 28 | // Make sure all the buttons are toolbar buttons 29 | var buttons = this.findAllComponents(); 30 | 31 | for (var i = 0; i < buttons.length; i++) 32 | { 33 | if (buttons[i].get('toolbar') !== undefined) 34 | { 35 | buttons[i].set('toolbar', true); 36 | } 37 | } 38 | }, 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /src/exporter-main/exporter-target/exporter-target.less: -------------------------------------------------------------------------------- 1 | @import "~core/ui/css/_variables.less"; 2 | @import "~core/ui/css/_common.less"; 3 | .theme(common); 4 | 5 | .exporter-target { 6 | display: block; 7 | .box-sizing; 8 | .unselectable; 9 | 10 | > * { 11 | margin-bottom: 0; 12 | } 13 | 14 | .core-dropdown, .core-combobox { 15 | width: auto; 16 | min-width: 45px; 17 | } 18 | 19 | label .core-checkbox { 20 | margin-right: @baseMargin; 21 | } 22 | 23 | input.error { 24 | border-color: @errorColor; 25 | } 26 | } 27 | 28 | .exporter-target(@theme) { 29 | .theme(@theme); 30 | 31 | .@{theme} { 32 | .exporter-target.target-details, .exporter-target.target-details .core-container { 33 | background: darken(@bodyBackground, 3%); 34 | } 35 | } 36 | } 37 | 38 | .exporter-target(dark); 39 | .exporter-target(medium-dark); 40 | .exporter-target(light); 41 | .exporter-target(medium-light); 42 | -------------------------------------------------------------------------------- /src/core/ui/core-menu/core-menu.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | .core-menu { 7 | .box-sizing; 8 | .unselectable; 9 | position: fixed; 10 | z-index: 1000; 11 | overflow-x: hidden; 12 | overflow-y: auto; 13 | color: #000; 14 | background: @menuBackground; 15 | 16 | > *:not([data-disabled=true]):not(.core-disabled):hover { 17 | background: @menuSelectedBackground; 18 | } 19 | 20 | > *[data-selected=true] { 21 | background: @menuSelectedBackground; 22 | } 23 | } 24 | 25 | .core-menu > * { 26 | display: block; 27 | white-space: nowrap; 28 | padding: 2px; 29 | min-height: 16x; 30 | max-height: 16px; 31 | } 32 | 33 | .menu(@theme) { 34 | .theme(@theme); 35 | 36 | .@{theme} .core-menu { 37 | border: @baseBorderSize solid @outlineColor; 38 | } 39 | } 40 | 41 | .menu(dark); 42 | .menu(medium-dark); 43 | .menu(light); 44 | .menu(medium-light); 45 | -------------------------------------------------------------------------------- /src/core/ui/core-dropdown/core-dropdown.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{label}}
4 | 5 |
6 | 7 | {{#options}} 8 | {{# { optionlabel: this[labelkey], optionvalue: this[valuekey], selectedvalue: ~/value } }} 9 | {{#if optionlabel === '---' }} 10 | 11 | {{else}} 12 | {{optionlabel}} 13 | {{/if}} 14 | {{/}} 15 | {{/options}} 16 | 17 |
18 | -------------------------------------------------------------------------------- /src/photoshop/ui/photoshop-panel/photoshop-panel.less: -------------------------------------------------------------------------------- 1 | @import "~core/ui/css/_variables.less"; 2 | @import "~core/ui/css/_common.less"; 3 | .theme(common); 4 | 5 | .connection-error { 6 | padding: @basePadding; 7 | font-size: @largeFontSize; 8 | line-height: @largeLineHeight; 9 | 10 | .title { 11 | font-weight: bold; 12 | } 13 | 14 | .details { 15 | color: @errorColor; 16 | } 17 | 18 | .settings { 19 | display: flex; 20 | 21 | label { 22 | width: 25%; 23 | } 24 | .core-textbox { 25 | flex: 1; 26 | } 27 | } 28 | 29 | .retry { 30 | text-align: center; 31 | } 32 | } 33 | 34 | .photoshop-panel(@theme) { 35 | .theme(@theme); 36 | 37 | .@{theme} { 38 | .connection-error .title { 39 | color: @textTitleColor; 40 | } 41 | } 42 | } 43 | 44 | .photoshop-panel(dark); 45 | .photoshop-panel(medium-dark); 46 | .photoshop-panel(light); 47 | .photoshop-panel(medium-light); 48 | 49 | -------------------------------------------------------------------------------- /src/photoshop/ui/photoshop-document/host/setXMPMetadata.jsx: -------------------------------------------------------------------------------- 1 | 2 | if (app.documents.length) 3 | { 4 | try 5 | { 6 | // Load XMP 7 | if (typeof ExternalObject.AdobeXMPScript !== 'object') 8 | { 9 | ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript'); 10 | } 11 | 12 | // Load current XMP metadata 13 | var xmpData = new XMPMeta(app.activeDocument.xmpMetadata.rawData); 14 | 15 | // Make sure our namespace is registered 16 | XMPMeta.registerNamespace(params.namespace, params.prefix); 17 | 18 | // Add new data 19 | xmpData.setProperty(params.namespace, params.key, params.data); 20 | 21 | // Save to file 22 | app.activeDocument.xmpMetadata.rawData = xmpData.serialize(); 23 | } 24 | catch (e) 25 | { } 26 | finally 27 | { 28 | // Unload XMP 29 | if (typeof ExternalObject.AdobeXMPScript === 'object') 30 | ExternalObject.AdobeXMPScript = undefined; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/photoshop/ui/photoshop-document/host/getXMPMetadata.jsx: -------------------------------------------------------------------------------- 1 | 2 | var data = ''; 3 | 4 | if (app.documents.length) 5 | { 6 | try 7 | { 8 | // Load XMP 9 | if (typeof ExternalObject.AdobeXMPScript !== 'object') 10 | { 11 | ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript'); 12 | } 13 | 14 | // If our namaspace is registered in XMP data, get data as string 15 | var xmpData = new XMPMeta(app.activeDocument.xmpMetadata.rawData); 16 | 17 | if (XMPMeta.getNamespacePrefix(params.namespace)) 18 | { 19 | if (xmpData.doesPropertyExist(params.namespace, params.key)) 20 | { 21 | data = xmpData.getProperty(params.namespace, params.key).value; 22 | } 23 | } 24 | } 25 | catch (e) 26 | {} 27 | finally 28 | { 29 | // Unload XMP 30 | if (typeof ExternalObject.AdobeXMPScript === 'object') 31 | ExternalObject.AdobeXMPScript = undefined; 32 | } 33 | } 34 | 35 | data; 36 | -------------------------------------------------------------------------------- /src/core/ui/core-menu-item/core-menu-item.js: -------------------------------------------------------------------------------- 1 | 2 | import CoreBase from '../core-base/core-base.js'; 3 | import './core-menu-item.less'; 4 | 5 | export default CoreBase.extend({ 6 | 7 | template: require('./core-menu-item.html'), 8 | 9 | data: { 10 | 11 | /** 12 | * Value for this menu item (i.e. used with dropdown lists). 13 | * @type String 14 | * @default null 15 | */ 16 | value: null, 17 | 18 | /** 19 | * Whether this menu item is currently selected (i.e. used with dropdown lists). 20 | * @type Boolean 21 | * @default false 22 | */ 23 | selected: false, 24 | 25 | /** 26 | * If true, this menu item won't be selectable by user. 27 | * @type Boolean 28 | * @default false 29 | */ 30 | disabled: false, 31 | 32 | /** 33 | * Whether this menu item is visible to user. 34 | * @type Boolean 35 | * @default true 36 | */ 37 | visible: true, 38 | 39 | }, 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /src/core/ui/core-dropdown/core-dropdown.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | .core-dropdown { 7 | display: inline-block; 8 | position: relative; 9 | width: 100%; 10 | 11 | .core-button-base { 12 | margin: 0; 13 | padding: 0 0 0 @basePadding; 14 | cursor: default; 15 | min-height: 20px !important; 16 | border-radius: @baseBorderRadius; 17 | } 18 | } 19 | 20 | .dropdown(@theme) { 21 | .theme(@theme); 22 | @import "../core-button/_button-states.less"; 23 | 24 | .@{theme} .core-dropdown { 25 | .core-button-base { 26 | .button-up; 27 | 28 | &:not(:disabled):active, 29 | &[data-active=true] { 30 | .button-down; 31 | border-color: @outlineColor; 32 | } 33 | 34 | &:disabled { 35 | .button-disabled; 36 | } 37 | } 38 | } 39 | } 40 | 41 | .dropdown(dark); 42 | .dropdown(medium-dark); 43 | .dropdown(light); 44 | .dropdown(medium-light); 45 | -------------------------------------------------------------------------------- /src/core/ui/core-textbox/core-textbox.js: -------------------------------------------------------------------------------- 1 | 2 | import CoreField from '../core-field/core-field.js'; 3 | import './core-textbox.less'; 4 | 5 | export default CoreField.extend({ 6 | 7 | template: require('./core-textbox.html'), 8 | 9 | twoway: true, 10 | 11 | data: { 12 | 13 | /** 14 | * Field value. 15 | * @type String 16 | * @default '' 17 | */ 18 | value: '', 19 | 20 | /** 21 | * Placeholder text that hints to the user what can be entered in the input. 22 | * @type String 23 | * @default null 24 | */ 25 | placeholder: null, 26 | 27 | /** 28 | * If true, the user cannot modify the value of the input. 29 | * @type Boolean 30 | * @default false 31 | */ 32 | readonly: false, 33 | 34 | }, 35 | 36 | keydownAction: function (event) 37 | { 38 | if (this.get('disabled') || this.get('readonly')) 39 | return; 40 | 41 | event.stopPropagation(); 42 | 43 | if (event.keyCode === 13) 44 | this.field.blur(); 45 | }, 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /src/core/ui/core-checkbox/core-checkbox.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | .core-checkbox { 7 | .box-sizing; 8 | min-width: 16px; 9 | min-height: 16px; 10 | max-width: 16px; 11 | max-height: 16px; 12 | border-radius: @baseBorderRadius; 13 | line-height: 1; 14 | } 15 | 16 | .core-checkbox[data-focused=true] { 17 | outline: none; 18 | border-bottom: @baseBorderSize solid @outlineColor; 19 | } 20 | 21 | .core-checkbox > input { 22 | display: none; 23 | } 24 | 25 | .checkbox(@theme) { 26 | .theme(@theme); 27 | @import "../core-button/_button-states.less"; 28 | 29 | .@{theme} .core-checkbox { 30 | .button-up; 31 | 32 | &[data-pressed=true] { 33 | .button-down; 34 | } 35 | 36 | &[data-disabled=true] { 37 | .button-disabled; 38 | 39 | & > .core-icon { 40 | color: @textDisabledColor !important; 41 | } 42 | } 43 | } 44 | } 45 | 46 | .checkbox(dark); 47 | .checkbox(medium-dark); 48 | .checkbox(light); 49 | .checkbox(medium-light); 50 | -------------------------------------------------------------------------------- /src/exporter-settings/settings-panel/settings-panel.js: -------------------------------------------------------------------------------- 1 | import Ractive from 'ractive'; 2 | import Extension from 'core/Extension.js'; 3 | import CEP from 'core/CEP.js'; 4 | import './settings-panel.less'; 5 | 6 | export default Ractive.extend({ 7 | 8 | template: require('./settings-panel.html'), 9 | 10 | // Append to document body 11 | el: document.body, 12 | append: true, 13 | 14 | data: { 15 | 16 | settings: null, 17 | 18 | generalSettingsVisible: true, 19 | tgaSettingsVisible: true, 20 | 21 | }, 22 | 23 | observer: null, 24 | 25 | oninit() 26 | { 27 | this._super(); 28 | this.observer = this.observe('settings.*', (value, old, path) => 29 | { 30 | let p = path.replace('settings.', ''); 31 | Extension.get().settings.set(p, value); 32 | 33 | }, { init: false }); 34 | this.reload(); 35 | }, 36 | 37 | reload() 38 | { 39 | this.observer.silence(); 40 | this.set('settings', Extension.get().settings.all()); 41 | this.observer.resume(); 42 | }, 43 | 44 | close() 45 | { 46 | CEP.closeExtension(); 47 | }, 48 | 49 | }); 50 | -------------------------------------------------------------------------------- /src/core/ui/core-icon/core-icon.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | 5 | // Entypo icons 6 | @font-face { 7 | font-family: 'entypo'; 8 | src: url('./entypo.woff') format("woff"); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | .core-icon { 14 | .box-sizing(); 15 | .unselectable(); 16 | display: inline-block; 17 | font-family: entypo; 18 | font-size: 14px; 19 | speak: none; 20 | font-style: normal; 21 | font-weight: normal; 22 | font-variant: normal; 23 | text-transform: none; 24 | letter-spacing: 0; 25 | line-height: 1em; 26 | -webkit-font-smoothing: antialiased; 27 | pointer-events: none; 28 | vertical-align: middle; 29 | } 30 | 31 | .icon(@theme) { 32 | .@{theme} { 33 | .theme(@theme); 34 | 35 | .core-icon { 36 | color: @btnTextColor; 37 | } 38 | 39 | [data-disabled=true], :disabled { 40 | .core-icon { 41 | color: @textDisabledColor !important; 42 | } 43 | } 44 | } 45 | } 46 | 47 | .icon(dark); 48 | .icon(medium-dark); 49 | .icon(light); 50 | .icon(medium-light); 51 | -------------------------------------------------------------------------------- /src/core/ui/core-combobox/core-combobox.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | .core-combobox { 7 | display: inline-block; 8 | position: relative; 9 | width: 100%; 10 | 11 | .core-button-base { 12 | margin: 0; 13 | padding: 0; 14 | cursor: default; 15 | border-radius: @baseBorderRadius; 16 | } 17 | 18 | .core-icon { 19 | width: 20px; 20 | text-align: center; 21 | } 22 | 23 | input { 24 | border: 0 !important; 25 | } 26 | } 27 | 28 | .combobox(@theme) { 29 | .theme(@theme); 30 | @import "../core-button/_button-states.less"; 31 | 32 | .@{theme} .core-combobox { 33 | .core-button-base { 34 | .button-up; 35 | 36 | &:not(:disabled):active, 37 | &[data-active=true] { 38 | .button-down; 39 | border-color: @outlineColor; 40 | } 41 | 42 | &:disabled { 43 | .button-disabled; 44 | } 45 | } 46 | } 47 | } 48 | 49 | .combobox(dark); 50 | .combobox(medium-dark); 51 | .combobox(light); 52 | .combobox(medium-light); 53 | -------------------------------------------------------------------------------- /src/exporter-main/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * Main Exporter Panel entry point. 5 | * 6 | */ 7 | 8 | import 'core'; 9 | import Extension from 'core/Extension.js'; 10 | import PhotoshopInterface from 'photoshop/PhotoshopInterface.js'; 11 | import ImageExporter from 'photoshop/ImageExporter.js'; 12 | 13 | import ExporterDefaultSettings from './DefaultSettings.json'; 14 | import ExporterPanel from './exporter-panel/exporter-panel.js'; 15 | 16 | const extension = Extension.create({ 17 | name: 'Exporter', 18 | vendor: 'Expresso', 19 | version: VERSION, 20 | folder: 'Expresso/Exporter', 21 | log: { filename: 'exporter-main.log' }, 22 | settings: { 23 | defaults: ExporterDefaultSettings, 24 | }, 25 | }); 26 | 27 | // Create the photoshop interfaces 28 | Object.defineProperty(extension, 'photoshop', { value: new PhotoshopInterface(), enumerable: true }); 29 | Object.defineProperty(extension, 'imageExporter', { value: new ImageExporter(extension.photoshop), enumerable: true }); 30 | extension.on('unload', () => { try { extension.photoshop.disconnect(); } catch (e) { } }); 31 | 32 | // Create the UI 33 | Object.defineProperty(extension, 'ui', { value: new ExporterPanel(), enumerable: true }); 34 | -------------------------------------------------------------------------------- /src/core/ui/core-numberbox/core-numberbox.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | .core-numberbox { 7 | .box-sizing; 8 | font: @baseFont; 9 | letter-spacing: @baseLetterSpacing; 10 | text-shadow: none; 11 | min-height: 20px; 12 | max-height: 20px; 13 | padding: @basePadding; 14 | -webkit-user-select: text !important; 15 | user-select: text !important; 16 | border: 0; 17 | background: transparent; 18 | 19 | &:focus { 20 | outline: none; 21 | } 22 | } 23 | 24 | .numberbox(@theme) { 25 | .@{theme} { 26 | .theme(@theme); 27 | 28 | .core-numberbox { 29 | color: @btnTextColor; 30 | border-bottom: @baseBorderSize solid @txtInputBorderColor; 31 | 32 | &:focus { 33 | border-bottom: @baseBorderSize solid @outlineColor; 34 | } 35 | 36 | &:disabled { 37 | color: @textDisabledColor; 38 | border-bottom: @baseBorderSize solid @baseBorderColorLight; 39 | } 40 | } 41 | } 42 | } 43 | 44 | .numberbox(dark); 45 | .numberbox(medium-dark); 46 | .numberbox(light); 47 | .numberbox(medium-light); 48 | -------------------------------------------------------------------------------- /src/exporter-settings/settings-panel/settings-panel.less: -------------------------------------------------------------------------------- 1 | @import "~core/ui/css/_variables.less"; 2 | @import "~core/ui/css/_common.less"; 3 | .theme(common); 4 | 5 | .core-extension { 6 | font-size: @largeFontSize; 7 | line-height: @largeLineHeight; 8 | } 9 | 10 | .core-container { 11 | min-height: 30px; 12 | } 13 | 14 | .settings-group, .settings-group-title { 15 | & > *{ 16 | margin-bottom: 0; 17 | } 18 | } 19 | 20 | .settings-group-title { 21 | .core-button-base { 22 | margin-right: @baseMargin * 2; 23 | } 24 | } 25 | 26 | .settings-group { 27 | .core-checkbox { 28 | margin-left: @baseMargin; 29 | margin-right: @baseMargin * 2; 30 | } 31 | } 32 | 33 | .settings-toolbar { 34 | padding: @basePadding; 35 | } 36 | 37 | .settings-panel(@theme) { 38 | .theme(@theme); 39 | 40 | .@{theme} { 41 | .settings-group-title { 42 | color: @textTitleColor; 43 | } 44 | 45 | .settings-group, .settings-group .core-container { 46 | background: darken(@bodyBackground, 3%); 47 | } 48 | } 49 | } 50 | 51 | .settings-panel(dark); 52 | .settings-panel(medium-dark); 53 | .settings-panel(light); 54 | .settings-panel(medium-light); 55 | -------------------------------------------------------------------------------- /src/photoshop/host/networkEventSubscribe.jsx: -------------------------------------------------------------------------------- 1 | // Built-in events 2 | var netEvents = ['imageChanged', 'generatorMenuChanged', 'generatorDocActivated', 'foregroundColorChanged', 3 | 'backgroundColorChanged', 'activeViewChanged', 'newDocumentViewCreated', 'closedDocument', 4 | 'documentChanged', 'colorSettingsChanged', 'quickMaskStateChanged', 'toolChanged', 5 | 'workspaceChanged', 'Asrt', 'idle']; 6 | 7 | function isNetEvent(event) 8 | { 9 | for (var i = 0, l = netEvents.length; i < l; i++) 10 | { 11 | if (event === netEvents[i]) 12 | { 13 | return true; 14 | } 15 | } 16 | 17 | return false; 18 | }; 19 | 20 | var actionDescriptor = new ActionDescriptor(); 21 | actionDescriptor.putString(stringIDToTypeID('version'), '1.0.0'); 22 | 23 | for (var i = 0, l = params.events.length; i < l; i++) 24 | { 25 | var event = params.events[i]; 26 | 27 | if (event.length > 4 || isNetEvent(event)) 28 | { 29 | actionDescriptor.putClass(stringIDToTypeID('eventIDAttr'), stringIDToTypeID(event)); 30 | } 31 | else 32 | { 33 | actionDescriptor.putClass(stringIDToTypeID('eventIDAttr'), charIDToTypeID(event)); 34 | } 35 | 36 | executeAction(stringIDToTypeID('networkEventSubscribe'), actionDescriptor, DialogModes.NO); 37 | } 38 | -------------------------------------------------------------------------------- /src/photoshop/ui/photoshop-panel/photoshop-panel.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

Can't connect to Photoshop.

5 |

{{connectionError}}

6 |

Please check that "Enable Remote Connection" is checked in "Preferences > Plug-Ins" and that the configuration 7 | matches the following one:

8 |

9 | 10 | 11 |

12 |

13 | 14 | 15 |

16 |

17 | 18 |

19 |
20 |
21 | {{>panel}} 22 |
23 | {{>toolbar}} 24 |
25 | -------------------------------------------------------------------------------- /src/core/ui/core-textbox/core-textbox.less: -------------------------------------------------------------------------------- 1 | @import "../css/_variables.less"; 2 | @import "../css/_common.less"; 3 | .theme(common); 4 | 5 | .core-textbox { 6 | .box-sizing; 7 | font: @baseFont; 8 | letter-spacing: @baseLetterSpacing; 9 | text-shadow: none; 10 | min-height: 20px; 11 | padding: @basePadding; 12 | width: 100%; 13 | -webkit-user-select: text !important; 14 | user-select: text !important; 15 | border: 0; 16 | background: transparent; 17 | 18 | &:focus { 19 | outline: none; 20 | } 21 | } 22 | 23 | input.core-textbox { 24 | max-height: 20px; 25 | } 26 | 27 | textarea.core-textbox { 28 | resize: none; 29 | } 30 | 31 | .textbox(@theme) { 32 | .@{theme} { 33 | .theme(@theme); 34 | 35 | .core-textbox { 36 | color: @btnTextColor; 37 | border-bottom: @baseBorderSize solid @txtInputBorderColor; 38 | 39 | &:focus { 40 | border-bottom: @baseBorderSize solid @outlineColor; 41 | } 42 | 43 | &:disabled { 44 | color: @textDisabledColor; 45 | border-bottom: @baseBorderSize solid @baseBorderColorLight; 46 | } 47 | } 48 | } 49 | } 50 | 51 | .textbox(dark); 52 | .textbox(medium-dark); 53 | .textbox(light); 54 | .textbox(medium-light); 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expresso", 3 | "version": "1.0.0", 4 | "license": "Apache-2.0", 5 | "private": true, 6 | "dependencies": { 7 | "bluebird": "^3.7.2", 8 | "extend": "^3.0.2", 9 | "fast.js": "latest", 10 | "path-is-absolute": "^2.0.0", 11 | "ractive": "^1.3.12", 12 | "ractive-events-tap": "^0.3.2", 13 | "sanitize-filename": "^1.6.3" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.12.9", 17 | "@babel/plugin-transform-for-of": "^7.12.1", 18 | "@babel/plugin-transform-strict-mode": "^7.12.1", 19 | "@babel/preset-env": "^7.12.7", 20 | "babel-loader": "^8.2.2", 21 | "cepy": "^0.2.2", 22 | "cpy": "^8.1.1", 23 | "css-loader": "^3.6.0", 24 | "cssnano": "^4.1.10", 25 | "cssnano-preset-advanced": "^4.0.7", 26 | "eslint": "^6.8.0", 27 | "htmlclean": "^3.0.8", 28 | "json-loader": "^0.5.7", 29 | "less": "^3.12.2", 30 | "less-loader": "^5.0.0", 31 | "mini-css-extract-plugin": "^0.9.0", 32 | "postcss-loader": "^3.0.0", 33 | "raw-loader": "^4.0.2", 34 | "rimraf": "^3.0.2", 35 | "uglifyjs-webpack-plugin": "^2.2.0", 36 | "url-loader": "^4.1.1", 37 | "webpack": "^4.44.2", 38 | "yuicompressor": "latest" 39 | }, 40 | "scripts": { 41 | "exporter:debug": "node bin/build.js exporter debug", 42 | "exporter:release": "node bin/build.js exporter release" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/core/ui/core-icon-button/core-icon-button.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | .core-icon-button { 7 | .box-sizing; 8 | padding: @basePadding; 9 | margin: 0 @baseMargin @baseMargin 0; 10 | border: @baseBorderSize solid transparent; 11 | border-radius: @baseBorderRadius; 12 | } 13 | 14 | .core-icon-button[data-toolbar=true] { 15 | padding: @basePadding @basePadding * 2; 16 | margin: 0; 17 | border-radius: 0; 18 | min-width: 30px; 19 | min-height: 26px; 20 | } 21 | 22 | .core-icon-button > .core-icon { 23 | display: block; 24 | margin-top: -@basePadding * 0.5; 25 | } 26 | 27 | .icon-button(@theme) { 28 | .theme(@theme); 29 | @import "../core-button/_button-states.less"; 30 | 31 | .@{theme} .core-icon-button { 32 | &:not(:disabled):not(:active):not([data-active=true]):hover { 33 | background: @btnBackgroundActiveAlt !important; 34 | border: @baseBorderSize solid @btnBorderColor !important; 35 | } 36 | 37 | &:not(:disabled):active, 38 | &[data-active=true] { 39 | .button-down; 40 | } 41 | 42 | &:disabled { 43 | color: @textDisabledColor; 44 | } 45 | } 46 | } 47 | 48 | .icon-button(dark); 49 | .icon-button(medium-dark); 50 | .icon-button(light); 51 | .icon-button(medium-light); 52 | -------------------------------------------------------------------------------- /src/core/ui/core-button/core-button.js: -------------------------------------------------------------------------------- 1 | 2 | import CoreField from '../core-field/core-field.js'; 3 | import './core-button.less'; 4 | 5 | export default CoreField.extend({ 6 | 7 | template: require('./core-button.html'), 8 | 9 | twoway: true, // Needed for toggleable buttons 10 | 11 | data: { 12 | 13 | /** 14 | * Gets or sets the label for this button. 15 | * @type String 16 | * @default '' 17 | */ 18 | label: '', 19 | 20 | /** 21 | * Gets or sets whether this a toggleable button. 22 | * @type Boolean 23 | * @default false 24 | */ 25 | toggleable: false, 26 | 27 | /** 28 | * If true, the button is currently active because the button 29 | * is toggleable and is currently in the active state. 30 | * @type boolean 31 | * @default false 32 | */ 33 | active: false, 34 | 35 | }, 36 | 37 | on: { 38 | 39 | tap: function (event) 40 | { 41 | if (!this.get('disabled') && this.get('toggleable')) 42 | { 43 | var active = !this.get('active'); 44 | this.set('active', active); 45 | this.fire('toggle', active); 46 | } 47 | }, 48 | 49 | }, 50 | 51 | cacheFieldElement: function () 52 | { 53 | this.field = this.find('button'); 54 | }, 55 | 56 | }); 57 | -------------------------------------------------------------------------------- /src/core/ui/core-button/core-button.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | // Used as a base for all buttons and button-like controls (dropdown, combobox, etc.) 7 | .core-button-base { 8 | .unselectable; 9 | .box-sizing; 10 | margin: 0 @baseMargin @baseMargin 0; 11 | display: inline-block; 12 | vertical-align: middle; 13 | outline: none; 14 | font: @baseFont; 15 | letter-spacing: @baseLetterSpacing; 16 | min-height: 20px; 17 | max-height: 20px; 18 | min-width: 24px; 19 | overflow: hidden; 20 | position: relative; 21 | cursor: pointer; 22 | background: transparent; 23 | text-align: left; 24 | 25 | &:focus { 26 | outline: none; 27 | } 28 | 29 | &:disabled { 30 | cursor: not-allowed; 31 | } 32 | } 33 | 34 | .core-button { 35 | padding: @basePadding @basePadding * 2; 36 | margin: 0 @baseMargin @baseMargin 0; 37 | border-radius: @baseBorderRadius; 38 | } 39 | 40 | .button(@theme) { 41 | .theme(@theme); 42 | @import "_button-states.less"; 43 | 44 | .@{theme} .core-button { 45 | .button-up; 46 | 47 | &:not(:disabled):active, 48 | &[data-active=true] { 49 | .button-down; 50 | } 51 | 52 | &:disabled { 53 | .button-disabled; 54 | } 55 | } 56 | } 57 | 58 | .button(dark); 59 | .button(medium-dark); 60 | .button(light); 61 | .button(medium-light); 62 | -------------------------------------------------------------------------------- /src/core/ui/css/_reset.less: -------------------------------------------------------------------------------- 1 | 2 | b, strong { 3 | font-weight: bold; 4 | } 5 | 6 | sub, sup { 7 | font-size: 75%; 8 | line-height: 0; 9 | position: relative; 10 | vertical-align: baseline; 11 | } 12 | 13 | sup { 14 | top: -0.5em; 15 | } 16 | 17 | sub { 18 | bottom: -0.25em; 19 | } 20 | 21 | pre { 22 | overflow: auto; 23 | } 24 | 25 | code, kbd, pre, samp { 26 | font-family: monospace, monospace; 27 | font-size: 1em; 28 | } 29 | 30 | button, input, optgroup, select, textarea { 31 | color: inherit; 32 | font: inherit; 33 | margin: 0; 34 | } 35 | 36 | button, 37 | input[type="button"], 38 | input[type="reset"], 39 | input[type="submit"] { 40 | -webkit-appearance: button; 41 | cursor: pointer; 42 | } 43 | 44 | input[type="number"]::-webkit-inner-spin-button, 45 | input[type="number"]::-webkit-outer-spin-button { 46 | height: auto; 47 | } 48 | 49 | input[type="search"] { 50 | -webkit-appearance: textfield; 51 | box-sizing: content-box; 52 | } 53 | 54 | input[type="search"]::-webkit-search-cancel-button, 55 | input[type="search"]::-webkit-search-decoration { 56 | -webkit-appearance: none; 57 | } 58 | 59 | fieldset { 60 | border: 0; 61 | margin: 0; 62 | padding: 0; 63 | } 64 | 65 | legend { 66 | border: 0; 67 | padding: 0; 68 | } 69 | 70 | textarea { 71 | overflow: auto; 72 | } 73 | 74 | optgroup { 75 | font-weight: bold; 76 | } 77 | -------------------------------------------------------------------------------- /src/core/ui/core-combobox/core-combobox.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | 7 | {{#each options as option}} 8 | {{#with 9 | option[labelkey] as label, 10 | option[valuekey] as value, 11 | option[visiblekey] as visible, 12 | (option[paddingkey] * option[paddingkey] * 2) as padding, 13 | (~/value === option[valuekey]) as selected 14 | }} 15 | {{#if label === '---' }} 16 | 17 | {{else}} 18 | 19 | {{#if padding > 2}}{{/if}} 20 | {{label}} 21 | 22 | {{/if}} 23 | {{/with}} 24 | {{/each}} 25 | 26 |
27 | -------------------------------------------------------------------------------- /src/core/ui/css/_container.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | .core-container { 7 | .box-sizing(); 8 | .unselectable(); 9 | width: 100%; 10 | padding: @basePadding; 11 | display: block; 12 | flex: 1; 13 | 14 | + .core-container, > .core-container { 15 | border-top: 0 !important; 16 | } 17 | 18 | > .core-container { 19 | background: darken(@bodyBackground, 3%); 20 | } 21 | } 22 | 23 | .container(@theme) { 24 | .theme(@theme); 25 | 26 | .@{theme} .core-container { 27 | border-bottom: @baseBorderSize solid @baseBorderColor; 28 | border-top: @baseBorderSize solid @baseBorderColorLight; 29 | background: @bodyBackground; 30 | 31 | &:first-child { 32 | border-top: 0; 33 | } 34 | 35 | &:last-child { 36 | border-bottom: 0; 37 | } 38 | 39 | &.core-container-horizontal { 40 | border-left: @baseBorderSize solid @baseBorderColor; 41 | border-right: @baseBorderSize solid @baseBorderColorLight; 42 | 43 | &:last-child { 44 | border-right: 0; 45 | } 46 | } 47 | 48 | &.core-container-both { 49 | border: @baseBorderSize solid @baseBorderColor; 50 | border-bottom: @baseBorderSize solid @baseBorderColorLight; 51 | } 52 | } 53 | } 54 | 55 | .container(dark); 56 | .container(medium-dark); 57 | .container(light); 58 | .container(medium-light); 59 | -------------------------------------------------------------------------------- /src/core/ui/core-colorbox/core-colorbox.js: -------------------------------------------------------------------------------- 1 | 2 | import CEP from '../../CEP.js'; 3 | import { defaultLogger as logger } from '../../Extension.js'; 4 | import CoreField from '../core-field/core-field.js'; 5 | 6 | import host from './host.jsx'; 7 | import './core-colorbox.less'; 8 | 9 | /** 10 | * A custom color field that looks more like the Photoshop one. 11 | */ 12 | export default CoreField.extend({ 13 | 14 | template: require('./core-colorbox.html'), 15 | 16 | twoway: true, 17 | 18 | data: { 19 | 20 | /** 21 | * Field value. 22 | * @type String 23 | * @default '' 24 | */ 25 | value: '', 26 | 27 | }, 28 | 29 | on: { 30 | 31 | colorClick: function () 32 | { 33 | if (this.get('disabled')) 34 | { 35 | return; 36 | } 37 | 38 | this.set('disabled', true); 39 | 40 | // Open Photoshop color picker 41 | CEP.evalScript(host, { baseColor: this.get('value') }).then((color) => 42 | { 43 | this.set('disabled', false); 44 | 45 | if (typeof color === 'string' && color.length === 7) 46 | { 47 | this.set('value', color); 48 | } 49 | else 50 | { 51 | throw new TypeError(`Invalid color: ${color}`); 52 | } 53 | 54 | }).catch((e) => 55 | { 56 | logger.error('Error while opening color picker', e); 57 | }); 58 | }, 59 | 60 | }, 61 | 62 | cacheFieldElement() 63 | { 64 | this.field = this.find('div'); 65 | }, 66 | 67 | }); 68 | -------------------------------------------------------------------------------- /src/core/SlowTask.js: -------------------------------------------------------------------------------- 1 | 2 | import { assert } from './assert.js'; 3 | import Extension from './Extension.js'; 4 | 5 | /** 6 | * The active slow task. 7 | * @type {String} 8 | */ 9 | let slowTask = null; 10 | 11 | /** 12 | * A global slow task. 13 | */ 14 | export default class SlowTask 15 | { 16 | 17 | /** 18 | * Gets the currently active slow task, if any. 19 | * @returns {SlowTask} 20 | */ 21 | static get() 22 | { 23 | return slowTask; 24 | } 25 | 26 | /** 27 | * Gets whether there is an active slow task. 28 | * @returns {Boolean} 29 | */ 30 | static isInProgress() 31 | { 32 | return (slowTask !== null); 33 | } 34 | 35 | /** 36 | * Creates a new slow task, ending the previous one (if any). 37 | * @param {String} message Initial message. 38 | */ 39 | static start(message) 40 | { 41 | SlowTask.complete(); 42 | slowTask = message; 43 | Extension.get().emit('busy', { status: true, task: slowTask }); 44 | } 45 | 46 | /** 47 | * Signals that there's been progress with the active slow task. 48 | * @param {String} message Initial message. 49 | */ 50 | static progress(message) 51 | { 52 | assert(slowTask !== null, 'No slow task is currently in progress.'); 53 | slowTask = message; 54 | Extension.get().emit('busy', { status: true, task: slowTask }); 55 | } 56 | 57 | /** 58 | * Completes the active slow task. 59 | */ 60 | static complete() 61 | { 62 | if (slowTask !== null) 63 | { 64 | Extension.get().emit('busy', { status: false, task: slowTask }); 65 | slowTask = null; 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/core/ui/core-slider/core-slider.less: -------------------------------------------------------------------------------- 1 | 2 | @import "../css/_variables.less"; 3 | @import "../css/_common.less"; 4 | .theme(common); 5 | 6 | .core-slider { 7 | .box-sizing; 8 | .unselectable; 9 | 10 | .thumb() { 11 | -webkit-appearance: none; 12 | width: 0; 13 | height: 0; 14 | padding-top: 8px; 15 | border-style: solid; 16 | border-width: 0 7px 9px 7px; 17 | border-radius: @baseBorderRadius; 18 | } 19 | 20 | .track() { 21 | padding: 0; 22 | -webkit-appearance: none; 23 | height: 3px; 24 | min-height: 3px; 25 | outline: none; 26 | } 27 | 28 | // Webkit uses base styling as track styling 29 | // Different than i.e. Firefox that needs a pseudo-element for track (::-moz-range-track) 30 | .track(); 31 | 32 | &:focus { 33 | .track(); 34 | } 35 | 36 | &::-webkit-slider-thumb { 37 | .thumb(); 38 | } 39 | 40 | &:disabled::-webkit-slider-thumb { 41 | -webkit-appearance: none; 42 | display: none; 43 | } 44 | } 45 | 46 | .slider(@theme) { 47 | .@{theme} .core-slider { 48 | .theme(@theme); 49 | 50 | .track() { 51 | background: @btnBorderColor; 52 | } 53 | 54 | // Webkit uses base styling as track styling 55 | .track(); 56 | 57 | &:disabled { 58 | .track(); 59 | } 60 | 61 | &:focus { 62 | .track(); 63 | } 64 | 65 | &::-webkit-slider-thumb { 66 | border-color: transparent transparent @rangeInputThumbBackground transparent; 67 | box-shadow: @rangeInputThumbBoxShadow; 68 | } 69 | } 70 | } 71 | 72 | .slider(dark); 73 | .slider(medium-dark); 74 | .slider(light); 75 | .slider(medium-light); 76 | -------------------------------------------------------------------------------- /src/core/ui/core-icon-button/core-icon-button.js: -------------------------------------------------------------------------------- 1 | 2 | import CoreButton from '../core-button/core-button.js'; 3 | import './core-icon-button.less'; 4 | 5 | export default CoreButton.extend({ 6 | 7 | template: require('./core-icon-button.html'), 8 | 9 | data: { 10 | 11 | /** 12 | * The icon for this button. 13 | * @type String 14 | * @default '' 15 | */ 16 | icon: '', 17 | 18 | /** 19 | * Icon used when the button is in the 'active' state, 20 | * fallback to the standard icon if not specified. 21 | * @type String 22 | * @default '' 23 | */ 24 | iconactive: '', 25 | 26 | /** 27 | * Icon used when the button is in the 'disabled' state, 28 | * fallback to the standard icon if not specified. 29 | * @type String 30 | * @default '' 31 | */ 32 | icondisabled: '', 33 | 34 | /** 35 | * Whether the button should be rendered with a 'toolbar' style. 36 | * @type Boolean 37 | * @default false 38 | */ 39 | toolbar: false, 40 | 41 | }, 42 | 43 | computed:{ 44 | 45 | currentIcon: function () 46 | { 47 | const disabledIcon = this.get('icondisabled'), 48 | activeIcon = this.get('iconactive'), 49 | disabled = this.get('disabled'), 50 | active = this.get('active'); 51 | 52 | let icon = this.get('icon'); 53 | if (disabled && disabledIcon.length) 54 | { 55 | icon = disabledIcon; 56 | } 57 | else if (active && activeIcon.length) 58 | { 59 | icon = activeIcon; 60 | } 61 | return icon; 62 | } 63 | 64 | }, 65 | 66 | }); 67 | -------------------------------------------------------------------------------- /src/core/ui/core-throbber/core-throbber.js: -------------------------------------------------------------------------------- 1 | 2 | import CoreBase from '../../ui/core-base/core-base.js'; 3 | import CoreVisible from '../../ui/core-visible/core-visible.js'; 4 | import CEP from '../../CEP.js'; 5 | import './core-throbber.less'; 6 | 7 | export default CoreBase.extend({ 8 | 9 | template: require('./core-throbber.html'), 10 | 11 | decorators: { 'core-visible': CoreVisible }, 12 | 13 | data: { 14 | 15 | /** 16 | * Whether the throbber should be visible. 17 | * @type Boolean 18 | * @default false 19 | */ 20 | visible: false, 21 | 22 | /** 23 | * Optional message shown alongside the throbber. 24 | * @type String 25 | * @default '' 26 | */ 27 | message: '', 28 | 29 | /** 30 | * Throbber size (in px). 31 | * @type Number 32 | * @default 150 33 | */ 34 | size: 150, 35 | 36 | }, 37 | 38 | oninit: function () 39 | { 40 | this._super(); 41 | 42 | // HACK: force a hierarchy refresh in CC2015. Needed to fix a bug 43 | // in CEF that prevents the overlay from being hidden correctly 44 | if (CEP.APIVersion.major === 6 && CEP.APIVersion.minor === 0) 45 | { 46 | this.observe('visible', (newValue) => 47 | { 48 | if (!newValue) 49 | { 50 | setTimeout(() => 51 | { 52 | let docElem = document.documentElement, docElemNext = docElem.nextSibling; 53 | document.removeChild(docElem); 54 | document.insertBefore(docElem, docElemNext); 55 | 56 | }, 50); 57 | } 58 | }, { defer: true }); 59 | } 60 | }, 61 | 62 | }); 63 | -------------------------------------------------------------------------------- /src/core/ui/core-field/core-field.js: -------------------------------------------------------------------------------- 1 | 2 | import CoreBase from '../core-base/core-base.js'; 3 | import './core-field.less'; 4 | 5 | export default CoreBase.extend({ 6 | 7 | data: { 8 | 9 | /** 10 | * Tooltip for this field. 11 | * @type String 12 | * @default null 13 | */ 14 | tooltip: null, 15 | 16 | /** 17 | * Field name. 18 | * @type String 19 | * @default null 20 | */ 21 | name: null, 22 | 23 | /** 24 | * If true, this field cannot be focused and the user cannot change 25 | * its value. 26 | * @type Boolean 27 | * @default false 28 | */ 29 | disabled: false, 30 | 31 | /** 32 | * Tabindex of this field. 33 | * @type String 34 | * @default null 35 | */ 36 | tabindex: null, 37 | 38 | }, 39 | 40 | oldTabindex: null, 41 | 42 | field: null, 43 | 44 | oninit: function () 45 | { 46 | this._super(); 47 | this.oldTabindex = this.get('tabindex'); 48 | this.observe('disabled', this.updateTabIndex); 49 | }, 50 | 51 | onrender: function () 52 | { 53 | this._super(); 54 | this.cacheFieldElement(); 55 | }, 56 | 57 | onunrender: function () 58 | { 59 | this.field = null; 60 | this._super(); 61 | }, 62 | 63 | cacheFieldElement: function () 64 | { 65 | this.field = this.find('input'); 66 | }, 67 | 68 | updateTabIndex: function () 69 | { 70 | if (this.get('disabled')) 71 | { 72 | this.oldTabindex = this.get('tabindex'); 73 | this.set('tabindex', null); 74 | } 75 | else 76 | { 77 | this.set('tabindex', this.oldTabindex); 78 | this.oldTabindex = null; 79 | } 80 | }, 81 | 82 | }); 83 | -------------------------------------------------------------------------------- /src/core/ui/css/core.less: -------------------------------------------------------------------------------- 1 | 2 | @import "./_common.less"; 3 | @import "./_variables.less"; 4 | .theme(common); 5 | 6 | // ******************************************** 7 | // Reset 8 | @import "./_reset.less"; 9 | 10 | // ******************************************** 11 | // Generic definitions 12 | 13 | html { 14 | height: 100%; 15 | overflow: hidden; 16 | margin: 0; 17 | padding: 0; 18 | } 19 | 20 | body { 21 | font: @baseFont; 22 | letter-spacing: @baseLetterSpacing; 23 | -webkit-font-smoothing: antialiased; 24 | cursor: default; 25 | width: 100%; 26 | height: 100%; 27 | position: relative; 28 | margin: 0; 29 | padding: 0; 30 | } 31 | 32 | p { 33 | margin-bottom: @baseMargin; 34 | } 35 | 36 | label { 37 | .box-sizing; 38 | .unselectable; 39 | display: inline-block; 40 | min-width: 40px; 41 | margin-right: @baseMargin; 42 | padding-right: @basePadding; 43 | vertical-align: middle; 44 | } 45 | 46 | // ******************************************** 47 | // Theme-specific styling (colors, etc.) 48 | .global(@theme) { 49 | .theme(@theme); 50 | 51 | .@{theme} { 52 | body { 53 | color: @textColor; 54 | background: @bodyBackground; 55 | } 56 | 57 | h1 { 58 | color: @textTitleColor; 59 | } 60 | 61 | a { 62 | color: @linkColor; 63 | 64 | &:hover { 65 | color: @linkColorHover; 66 | } 67 | } 68 | 69 | label { 70 | &[data-disabled="true"] { 71 | color: @textDisabledColor; 72 | } 73 | } 74 | 75 | .core-disabled { 76 | color: @textDisabledColor; 77 | } 78 | } 79 | } 80 | 81 | .global(dark); 82 | .global(medium-dark); 83 | .global(light); 84 | .global(medium-light); 85 | 86 | @import "./_scrollbars.less"; 87 | @import "./_layout.less"; 88 | @import "./_container.less"; 89 | -------------------------------------------------------------------------------- /src/core/ui/core-throbber/core-throbber.less: -------------------------------------------------------------------------------- 1 | @import "../../ui/css/_variables.less"; 2 | @import "../../ui/css/_common.less"; 3 | .theme(common); 4 | 5 | .core-throbber-veil { 6 | .unselectable; 7 | position: absolute; 8 | z-index: 999999; 9 | top: 0; 10 | bottom: 0; 11 | right: 0; 12 | left: 0; 13 | display: flex; 14 | justify-content: center; 15 | align-items: center; 16 | flex-direction: column; 17 | } 18 | 19 | .core-throbber-image { 20 | background-image: url('./throbber.png'); 21 | background-size: 75%; 22 | background-position: center 20%; 23 | background-repeat: no-repeat; 24 | position: fixed; 25 | top: 50%; 26 | left: 50%; 27 | transform: translate(-50%, -50%); 28 | } 29 | 30 | .core-throbber-animation { 31 | // Based on: http://projects.lukehaas.me/css-loaders/ 32 | animation: core-throbber-keys 1.5s infinite linear; 33 | border-radius: 50%; 34 | border: 8px solid transparent; 35 | transform: translate(-50%, -50%); 36 | } 37 | 38 | .core-throbber-message { 39 | margin-top: 16px; 40 | text-align: center; 41 | pointer-events: none; 42 | } 43 | 44 | .throbber(@theme) { 45 | .theme(@theme); 46 | 47 | .@{theme} { 48 | .core-throbber-veil { 49 | background-color: fadeout(darken(@bodyBackground, 40%), 50%); 50 | } 51 | 52 | .core-throbber-animation { 53 | border-left-color: fadeout(@rangeInputThumbBackground, 50%); // FIXME: add dedicated variable 54 | } 55 | 56 | .core-throbber-message { 57 | color: @rangeInputThumbBackground; // FIXME: add dedicated variable 58 | } 59 | } 60 | } 61 | 62 | .throbber(dark); 63 | .throbber(medium-dark); 64 | .throbber(light); 65 | .throbber(medium-light); 66 | 67 | @keyframes core-throbber-keys { 68 | 0% { 69 | transform: rotate(0deg); 70 | } 71 | 72 | 100% { 73 | transform: rotate(360deg); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/photoshop/Pixmap.js: -------------------------------------------------------------------------------- 1 | // Original code: 2 | /* 3 | * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the 10 | * Software is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all 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 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | * DEALINGS IN THE SOFTWARE. 22 | * 23 | */ 24 | 25 | /** 26 | * A raw image buffer received from Photoshop. 27 | */ 28 | export default class Pixmap 29 | { 30 | constructor(buffer) 31 | { 32 | this.format = buffer.readUInt8(0); 33 | this.width = buffer.readUInt32BE(1); 34 | this.height = buffer.readUInt32BE(5); 35 | this.rowBytes = buffer.readUInt32BE(9); 36 | this.colorMode = buffer.readUInt8(13); 37 | this.channelCount = buffer.readUInt8(14); 38 | this.bitsPerChannel = buffer.readUInt8(15); 39 | this.pixels = buffer.slice(16, 16 + this.width * this.height * this.channelCount); 40 | this.bytesPerPixel = this.bitsPerChannel / (8 * this.channelCount); 41 | this.padding = this.rowBytes - (this.width * this.channelCount); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/photoshop/ExportTarget.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Defines export settings for a single output image. 4 | */ 5 | export default class ExportTarget 6 | { 7 | constructor() 8 | { 9 | Object.defineProperties(this, { 10 | 11 | /** Export path. Only used when exporting to file. */ 12 | path: { value: '', enumerable: true, writable: true }, 13 | 14 | /** Export format (@see ImageExporter.FORMATS). */ 15 | format: { value: 'tga', enumerable: true, writable: true }, 16 | 17 | /** LayerID <-> channel map. Specifies which layer should be saved into each channel of the output image (RGBA). */ 18 | channels: { value: [-1, -1, -1, -1], enumerable: true, writable: false }, 19 | 20 | /** Whether RGB channels should be locked together (that is, the layer specified for the RED channel will be used for GREEN and BLUE as-well). */ 21 | channelsLocked: { value: true, enumerable: true, writable: true }, 22 | 23 | /** Export scale (1.0 means no scaling). */ 24 | scale: { value: 1.0, enumerable: true, writable: true }, 25 | 26 | /** Export filters. */ 27 | filters: { 28 | value: Object.create(null, { 29 | blur: { value: false, enumerable: true, writable: true }, 30 | sharpen: { value: false, enumerable: true, writable: true }, 31 | invert: { value: false, enumerable: true, writable: true }, 32 | }), 33 | enumerable: true, 34 | writable: false, 35 | }, 36 | 37 | /** Special filters that only apply to normal maps. */ 38 | normal: { 39 | value: Object.create(null, { 40 | normalize: { value: false, enumerable: true, writable: true }, 41 | flipX: { value: false, enumerable: true, writable: true }, 42 | flipY: { value: false, enumerable: true, writable: true }, 43 | flipZ: { value: false, enumerable: true, writable: true }, 44 | }), 45 | enumerable: true, 46 | writable: false, 47 | }, 48 | 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/core/ui/css/_layout.less: -------------------------------------------------------------------------------- 1 | 2 | //******************************* 3 | // Flex Layout 4 | // Based on http://www.polymer-project.org/components/polymer/layout.html 5 | //******************************* 6 | 7 | .core-layout.horizontal, .core-layout.vertical { 8 | display: flex; 9 | } 10 | 11 | .core-layout.horizontal.inline, .core-layout.vertical.inline { 12 | display: inline-flex; 13 | } 14 | 15 | .core-layout.horizontal { 16 | flex-direction: row; 17 | min-height: 24px; 18 | } 19 | 20 | .core-layout.horizontal.reverse { 21 | flex-direction: row-reverse; 22 | } 23 | 24 | .core-layout.vertical { 25 | flex-direction: column; 26 | } 27 | 28 | .core-layout.vertical.reverse { 29 | flex-direction: column-reverse; 30 | } 31 | 32 | .core-layout.wrap { 33 | flex-wrap: wrap; 34 | } 35 | 36 | .core-layout.wrap-reverse { 37 | flex-wrap: wrap-reverse; 38 | } 39 | 40 | .flex { 41 | flex: 1; 42 | } 43 | 44 | .flex.auto { 45 | flex: 1 1 auto; 46 | } 47 | 48 | .flex.none { 49 | flex: none; 50 | } 51 | 52 | .flex.one { 53 | flex: 1; 54 | } 55 | 56 | .flex.two { 57 | flex: 2; 58 | } 59 | 60 | .flex.three { 61 | flex: 3; 62 | } 63 | 64 | .flex.four { 65 | flex: 4; 66 | } 67 | 68 | .flex.five { 69 | flex: 5; 70 | } 71 | 72 | .flex.six { 73 | flex: 6; 74 | } 75 | 76 | .flex.seven { 77 | flex: 7; 78 | } 79 | 80 | .flex.eight { 81 | flex: 8; 82 | } 83 | 84 | .flex.nine { 85 | flex: 9; 86 | } 87 | 88 | .flex.ten { 89 | flex: 10; 90 | } 91 | 92 | .flex.eleven { 93 | flex: 11; 94 | } 95 | 96 | .flex.twelve { 97 | flex: 12; 98 | } 99 | 100 | // Alignment in cross axis 101 | 102 | .core-layout.start { 103 | align-items: flex-start; 104 | } 105 | 106 | .core-layout.center { 107 | align-items: center; 108 | } 109 | 110 | .core-layout.end { 111 | align-items: flex-end; 112 | } 113 | 114 | // Alignment in main axis 115 | 116 | .core-layout.start-justified { 117 | justify-content: flex-start; 118 | } 119 | 120 | .core-layout.center-justified { 121 | justify-content: center; 122 | } 123 | 124 | .core-layout.end-justified { 125 | justify-content: flex-end; 126 | } 127 | 128 | .core-layout.around-justified { 129 | justify-content: space-around; 130 | } 131 | 132 | .core-layout.justified { 133 | justify-content: space-between; 134 | } 135 | 136 | // Self alignment 137 | 138 | .self-start { 139 | align-self: flex-start; 140 | } 141 | 142 | .self-center { 143 | align-self: center; 144 | } 145 | 146 | .self-end { 147 | align-self: flex-end; 148 | } 149 | 150 | .self-stretch { 151 | align-self: stretch; 152 | } 153 | -------------------------------------------------------------------------------- /src/photoshop/PhotoshopCrypto.js: -------------------------------------------------------------------------------- 1 | // Original code: 2 | /* 3 | * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the 10 | * Software is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all 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 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | * DEALINGS IN THE SOFTWARE. 22 | * 23 | */ 24 | 25 | import { createDecipheriv, createCipheriv, pbkdf2 } from 'crypto'; 26 | 27 | // Constants 28 | const SALT = 'Adobe Photoshop', 29 | NUM_ITERATIONS = 1000, 30 | ALGORITHM = 'des-ede3-cbc', 31 | KEY_LENGTH = 24, 32 | IV = new Buffer('000000005d260000', 'hex'); // 0000 0000 5d26 0000 33 | 34 | /** 35 | * PhotoshopCrypto 36 | */ 37 | class PhotoshopCrypto 38 | { 39 | constructor(derivedKey) 40 | { 41 | this._derivedKey = derivedKey; 42 | } 43 | 44 | decipher(buf) 45 | { 46 | const d = createDecipheriv(ALGORITHM, this._derivedKey, IV); 47 | return new Buffer(d.update(buf, 'binary', 'binary') + d.final('binary'), 'binary'); 48 | } 49 | 50 | cipher(buf) 51 | { 52 | const c = createCipheriv(ALGORITHM, this._derivedKey, IV); 53 | return new Buffer(c.update(buf, 'binary', 'binary') + c.final('binary'), 'binary'); 54 | } 55 | 56 | } 57 | 58 | export function createPhotoshopCrypto(password, callback) 59 | { 60 | const pbkdf2Callback = function (err, derivedKey) 61 | { 62 | if (err) 63 | { 64 | callback(err, null); 65 | } 66 | else 67 | { 68 | callback(null, new PhotoshopCrypto(derivedKey)); 69 | } 70 | }; 71 | 72 | // Phothoshop CC 2015 ships with NodeJS 0.8.2 which doesn't support the [digest] parameter in the function signature 73 | if (process.version === 'v0.8.22') 74 | { 75 | pbkdf2(password, SALT, NUM_ITERATIONS, KEY_LENGTH, pbkdf2Callback); 76 | } 77 | else 78 | { 79 | pbkdf2(password, SALT, NUM_ITERATIONS, KEY_LENGTH, 'sha1', pbkdf2Callback); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/core/ui/core-checkbox/core-checkbox.js: -------------------------------------------------------------------------------- 1 | 2 | import CoreField from '../core-field/core-field.js'; 3 | import './core-checkbox.less'; 4 | 5 | export default CoreField.extend({ 6 | 7 | template: require('./core-checkbox.html'), 8 | 9 | twoway: true, 10 | 11 | data: { 12 | 13 | /** 14 | * Field value. 15 | * @type Boolean 16 | * @default false 17 | */ 18 | value: false, 19 | 20 | /** 21 | * If true, the element currently has focus due to keyboard 22 | * navigation. 23 | * 24 | * @attribute focused 25 | * @type boolean 26 | * @default false 27 | */ 28 | focused: false, 29 | 30 | /** 31 | * If true, the user is currently holding down the button. 32 | * 33 | * @attribute pressed 34 | * @type boolean 35 | * @default false 36 | */ 37 | pressed: false, 38 | 39 | /** 40 | * Tabindex of this field. 41 | * @type String 42 | * @default null 43 | */ 44 | tabindex: 0, 45 | 46 | }, 47 | 48 | computed: { 49 | 50 | icon: function () { return this.get('value') ? 'checkmark' : ''; } 51 | 52 | }, 53 | 54 | focusAction: function () 55 | { 56 | if (this.get('disabled')) 57 | { 58 | return; 59 | } 60 | 61 | if (!this.get('pressed')) 62 | { 63 | // Only render the "focused" state if the element gains focus due to 64 | // keyboard navigation. 65 | this.set('focused', true); 66 | } 67 | }, 68 | 69 | blurAction: function () 70 | { 71 | if (this.get('disabled')) 72 | { 73 | return; 74 | } 75 | 76 | this.set('focused', false); 77 | }, 78 | 79 | downAction: function () 80 | { 81 | if (this.get('disabled')) 82 | { 83 | return; 84 | } 85 | 86 | this.set('pressed', true); 87 | this.set('focused', false); 88 | }, 89 | 90 | clickAction: function (event) 91 | { 92 | if (this.get('disabled')) 93 | { 94 | return; 95 | } 96 | 97 | // HACK: when core-checkbox is wrapped into a