summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrLi2020-12-07 16:08:40 +0800
committerBrLi2020-12-07 16:08:40 +0800
commit3e212e864fe9e223a44f301dc6dae614854beae7 (patch)
tree1f09f2681eeb32ee38c8100aa065bfce6c3b0351
parent760529b50c29e7624621fbbfac2081e1d2b7c3eb (diff)
downloadaur-3e212e864fe9e223a44f301dc6dae614854beae7.tar.gz
introduce upstream fix for various bugs
-rw-r--r--.SRCINFO10
-rw-r--r--PKGBUILD16
-rw-r--r--fix-package-json.patch (renamed from fixed-package-json.patch)27
-rw-r--r--upstream.patch5539
4 files changed, 5576 insertions, 16 deletions
diff --git a/.SRCINFO b/.SRCINFO
index 3066f6f7f10c..beaddf613818 100644
--- a/.SRCINFO
+++ b/.SRCINFO
@@ -1,18 +1,20 @@
pkgbase = pencil
pkgdesc = Sketching and GUI prototyping/wireframing tool
pkgver = 3.1.0
- pkgrel = 6
+ pkgrel = 7
url = https://github.com/evolus/pencil
arch = any
license = GPL2
makedepends = yarn
- depends = electron9
+ depends = electron
conflicts = evolus-pencil-bin
conflicts = pencil-v2
source = https://github.com/evolus/pencil/archive/v3.1.0.tar.gz
- source = fixed-package-json.patch
+ source = fix-package-json.patch
+ source = upstream.patch
sha256sums = e14eddd0aad28919cfdf8d47b726f9c75a3a0d2042605e8da96309c23a995f44
- sha256sums = ab36a7476d4a04dc684441f67faa3f7f84168a725b4e977d467e0b4321eb0d50
+ sha256sums = 7094d33707a1fa27b79f296b3083584643150935ac4e464ebd44a82ed04ad036
+ sha256sums = 78c28950a497495f3efef0b915283c10fd834c83996471291ae6ac18e3256997
pkgname = pencil
diff --git a/PKGBUILD b/PKGBUILD
index bb9a69cdd2ee..9b457d168092 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -3,24 +3,28 @@
pkgname=pencil
pkgver=3.1.0
-pkgrel=6
+pkgrel=7
pkgdesc="Sketching and GUI prototyping/wireframing tool"
arch=('any')
license=('GPL2')
url="https://github.com/evolus/pencil"
-depends=(electron9)
+depends=(electron)
makedepends=(yarn)
source=("https://github.com/evolus/pencil/archive/v$pkgver.tar.gz"
- 'fixed-package-json.patch')
+ 'fix-package-json.patch'
+ 'upstream.patch')
sha256sums=('e14eddd0aad28919cfdf8d47b726f9c75a3a0d2042605e8da96309c23a995f44'
- 'ab36a7476d4a04dc684441f67faa3f7f84168a725b4e977d467e0b4321eb0d50')
+ '7094d33707a1fa27b79f296b3083584643150935ac4e464ebd44a82ed04ad036'
+ '78c28950a497495f3efef0b915283c10fd834c83996471291ae6ac18e3256997')
conflicts=('evolus-pencil-bin' 'pencil-v2')
prepare() {
cd "${srcdir}/${pkgname}-${pkgver}"
+
+ patch -Np1 -i "${srcdir}/upstream.patch"
# We don't build electron and friends, and don't depends on postinstall script
- patch -Np1 -i "${srcdir}/fixed-package-json.patch"
+ patch -Np1 -i "${srcdir}/fix-package-json.patch"
sed '/^\s*\"electron.*$/d;/postinstall/d' -i app/package.json
}
@@ -57,7 +61,7 @@ package() {
install -Dm755 /dev/stdin "${pkgdir}/usr/bin/${pkgname}" <<END
#!/bin/sh
-exec electron9 /${_destdir} "\$@"
+exec electron /${_destdir} "\$@"
END
cd "${srcdir}/${pkgname}-${pkgver}"
diff --git a/fixed-package-json.patch b/fix-package-json.patch
index 6d9227a96cf2..a7e61c18207a 100644
--- a/fixed-package-json.patch
+++ b/fix-package-json.patch
@@ -1,10 +1,21 @@
---- a/package.json 2019-10-18 19:38:10.000000000 +0800
-+++ b/package.json 2020-10-12 11:52:41.353799990 +0800
+From f6b62008495253f0c68ea1a03eb6815e3bd60e97 Mon Sep 17 00:00:00 2001
+From: BrLi <brli@chakralinux.org>
+Date: Mon, 7 Dec 2020 15:58:13 +0800
+Subject: [PATCH] fix package.json
+
+---
+ package.json | 8 --------
+ 1 file changed, 8 deletions(-)
+
+diff --git a/package.json b/package.json
+index 9c2d20c..4ad8321 100644
+--- a/package.json
++++ b/package.json
@@ -1,9 +1,6 @@
{
"name": "Pencil",
"devDependencies": {
-- "electron": "6.0.1",
+- "electron": "10.1.5",
- "electron-builder": "20.28.4",
- "electron-rebuild": "^1.8.5",
"rimraf": "^2.5.4"
@@ -20,11 +31,15 @@
"fileAssociations": {
"ext": [
"ep",
-@@ -68,7 +62,6 @@
+@@ -68,8 +62,6 @@
}
},
"scripts": {
- "postinstall": "install-app-deps",
- "install-app-deps": "node ./node_modules/electron-builder/out/install-app-deps.js",
+- "install-app-deps": "node ./node_modules/electron-builder/out/install-app-deps.js",
"start": "./node_modules/.bin/electron ./app",
- "start:dev": "./node_modules/.bin/electron ./app --enable-dev --enable-transparent-visuals --disable-gpu",
+ "start:dev": "./node_modules/.bin/electron ./app --enable-dev --enable-transparent-visuals",
+ "start:mac": "./node_modules/.bin/electron ./app --enable-dev",
+--
+2.29.2
+
diff --git a/upstream.patch b/upstream.patch
new file mode 100644
index 000000000000..c71cb18b9d73
--- /dev/null
+++ b/upstream.patch
@@ -0,0 +1,5539 @@
+diff --git a/app/app.xhtml b/app/app.xhtml
+index fa1f556..869a5f6 100644
+--- a/app/app.xhtml
++++ b/app/app.xhtml
+@@ -4,7 +4,7 @@
+ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://evolus.vn/Namespaces/WebUI/1.0">
+ <head>
+ <title>Pencil</title>
+-
++ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <link rel="stylesheet" href="css/material-icons.css" />
+
+ <link rel="stylesheet" href="css/pencil.css"/>
+@@ -15,6 +15,7 @@
+ <script type="text/javascript" src="lib/codemirror/codemirror.js"></script>
+ <link rel="stylesheet" href="lib/codemirror/codemirror.css"/>
+ <script type="text/javascript" src="lib/codemirror/javascript.js"></script>
++ <script type="text/javascript" src="lib/codemirror/xml.js"></script>
+
+ <!-- Pencil Core -->
+ <script type="text/javascript" src="pencil-core/common/pencilNamespaces.js"></script>
+@@ -46,6 +47,7 @@
+ <script type="text/javascript" src="pencil-core/canvasHelper/canvasMemento.js"></script>
+ <script type="text/javascript" src="pencil-core/canvasHelper/canvasImpl.js"></script>
+ <script type="text/javascript" src="pencil-core/canvasHelper/snappingHelper.js"></script>
++ <script type="text/javascript" src="pencil-core/canvasHelper/GestureHelper.js"></script>
+
+ <script type="text/javascript" src="pencil-core/privateCollection/privateCollection.js"></script>
+ <script type="text/javascript" src="pencil-core/privateCollection/privateShapeDef.js"></script>
+@@ -147,7 +149,7 @@
+
+ <script type="text/javascript" src="pencil-core/clipartBrowser/webUtil.js"></script>
+ <script type="text/javascript" src="pencil-core/clipartBrowser/searchEngine.js"></script>
+- <script type="text/javascript" src="pencil-core/clipartBrowser/openClipartSearch.js"></script>
++ <script type="text/javascript" src="pencil-core/clipartBrowser/newOpenClipartSearch.js"></script>
+
+ <!--<script type="text/javascript" src="pencil-core/mainWindow.js"></script>-->
+
+diff --git a/app/css/theme.css b/app/css/theme.css
+index 8db875f..bccd7c3 100644
+--- a/app/css/theme.css
++++ b/app/css/theme.css
+@@ -18,7 +18,7 @@ button {
+ white-space: nowrap;
+ vertical-align: middle;
+ cursor: pointer;
+- border: 1px solid #CCC;
++ border: none;
+ border-radius: 4px;
+
+ line-height: 2.4em;
+@@ -27,11 +27,11 @@ button {
+
+ border-radius: 0.3ex;
+
+- background: #FFF;
++ background: #FEFEFE;
++ color: #000000DD;
+ outline: none;
+
+ text-shadow: 0px 1px 0px #FFF;
+- background-image: linear-gradient(to bottom, #FFF 0px, #E0E0E0 100%);
+ background-repeat: repeat-x;
+ display: inline-flex;
+ align-items: center;
+diff --git a/app/index.js b/app/index.js
+index 4e0ac9e..79fec6a 100644
+--- a/app/index.js
++++ b/app/index.js
+@@ -4,23 +4,36 @@ const {app, protocol, shell, BrowserWindow} = require("electron");
+ const pkg = require("./package.json");
+ const fs = require("fs");
+ const path = require("path");
++const os = require("os");
+
+-app.commandLine.appendSwitch("allow-file-access-from-files");
+-app.commandLine.appendSwitch("allow-file-access");
++app.commandLine.appendSwitch("no-sandbox");
++app.commandLine.appendSwitch("allow-file-access-from-files", "1");
++app.commandLine.appendSwitch("allow-file-access", "1");
+ app.commandLine.appendSwitch("disable-smooth-scrolling");
+ app.commandLine.appendSwitch("disable-site-isolation-trials");
+
+ // Disable hardware acceleration by default for Linux
+ // TODO: implement a setting for this one and requires a restart after changing that value
+ if (process.platform.trim().toLowerCase() == "linux" && app.disableHardwareAcceleration) {
+- if (process.argv.indexOf("--with-hwa") < 0) {
++ var useHWAConfig = getAppConfig("core.useHardwareAcceleration");
++ console.log("useHWAConfig: ", useHWAConfig);
++ if (process.argv.indexOf("--with-hwa") < 0 && !useHWAConfig) {
+ console.log("Hardware acceleration disabled for Linux.");
+ app.disableHardwareAcceleration();
+ } else {
+ console.log("Hardware acceleration forcibly enabled.");
+ }
+ }
+-
++function getAppConfig(name) {
++ var p = path.join(path.join(os.homedir(), ".pencil"), "config.json");
++ try {
++ var json = fs.readFileSync(p, "utf8");
++ var data = JSON.parse(json);
++ return data[name];
++ } catch (e) {
++ return undefined;
++ }
++}
+ global.sharedObject = { appArguments: process.argv };
+
+ var handleRedirect = (e, url) => {
+@@ -38,7 +51,8 @@ function createWindow() {
+ allowRunningInsecureContent: true,
+ allowDisplayingInsecureContent: true,
+ defaultEncoding: "UTF-8",
+- nodeIntegration: true
++ nodeIntegration: true,
++ enableRemoteModule: true
+ },
+ };
+
+@@ -60,7 +74,7 @@ function createWindow() {
+ mainWindow.maximize();
+
+ if (devEnable) {
+- mainWindow.webContents.openDevTools();
++ //mainWindow.webContents.openDevTools();
+ } else {
+ mainWindow.setMenu(null);
+ }
+@@ -106,9 +120,9 @@ app.on('ready', function() {
+
+ fs.readFile(path, function (err, data) {
+ if (err) {
+- callback({mimeType: "text/html", data: new Buffer("Not found")});
++ callback({mimeType: "text/html", data: Buffer.from("Not found")});
+ } else {
+- callback({mimeType: "image/jpeg", data: new Buffer(data)});
++ callback({mimeType: "image/jpeg", data: Buffer.from(data)});
+ }
+ });
+
+diff --git a/app/lib/codemirror/xml.js b/app/lib/codemirror/xml.js
+new file mode 100644
+index 0000000..73c6e0e
+--- /dev/null
++++ b/app/lib/codemirror/xml.js
+@@ -0,0 +1,413 @@
++// CodeMirror, copyright (c) by Marijn Haverbeke and others
++// Distributed under an MIT license: https://codemirror.net/LICENSE
++
++(function(mod) {
++ if (typeof exports == "object" && typeof module == "object") // CommonJS
++ mod(require("../../lib/codemirror"));
++ else if (typeof define == "function" && define.amd) // AMD
++ define(["../../lib/codemirror"], mod);
++ else // Plain browser env
++ mod(CodeMirror);
++})(function(CodeMirror) {
++"use strict";
++
++var htmlConfig = {
++ autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
++ 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
++ 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
++ 'track': true, 'wbr': true, 'menuitem': true},
++ implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
++ 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
++ 'th': true, 'tr': true},
++ contextGrabbers: {
++ 'dd': {'dd': true, 'dt': true},
++ 'dt': {'dd': true, 'dt': true},
++ 'li': {'li': true},
++ 'option': {'option': true, 'optgroup': true},
++ 'optgroup': {'optgroup': true},
++ 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
++ 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
++ 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
++ 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
++ 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
++ 'rp': {'rp': true, 'rt': true},
++ 'rt': {'rp': true, 'rt': true},
++ 'tbody': {'tbody': true, 'tfoot': true},
++ 'td': {'td': true, 'th': true},
++ 'tfoot': {'tbody': true},
++ 'th': {'td': true, 'th': true},
++ 'thead': {'tbody': true, 'tfoot': true},
++ 'tr': {'tr': true}
++ },
++ doNotIndent: {"pre": true},
++ allowUnquoted: true,
++ allowMissing: true,
++ caseFold: true
++}
++
++var xmlConfig = {
++ autoSelfClosers: {},
++ implicitlyClosed: {},
++ contextGrabbers: {},
++ doNotIndent: {},
++ allowUnquoted: false,
++ allowMissing: false,
++ allowMissingTagName: false,
++ caseFold: false
++}
++
++CodeMirror.defineMode("xml", function(editorConf, config_) {
++ var indentUnit = editorConf.indentUnit
++ var config = {}
++ var defaults = config_.htmlMode ? htmlConfig : xmlConfig
++ for (var prop in defaults) config[prop] = defaults[prop]
++ for (var prop in config_) config[prop] = config_[prop]
++
++ // Return variables for tokenizers
++ var type, setStyle;
++
++ function inText(stream, state) {
++ function chain(parser) {
++ state.tokenize = parser;
++ return parser(stream, state);
++ }
++
++ var ch = stream.next();
++ if (ch == "<") {
++ if (stream.eat("!")) {
++ if (stream.eat("[")) {
++ if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
++ else return null;
++ } else if (stream.match("--")) {
++ return chain(inBlock("comment", "-->"));
++ } else if (stream.match("DOCTYPE", true, true)) {
++ stream.eatWhile(/[\w\._\-]/);
++ return chain(doctype(1));
++ } else {
++ return null;
++ }
++ } else if (stream.eat("?")) {
++ stream.eatWhile(/[\w\._\-]/);
++ state.tokenize = inBlock("meta", "?>");
++ return "meta";
++ } else {
++ type = stream.eat("/") ? "closeTag" : "openTag";
++ state.tokenize = inTag;
++ return "tag bracket";
++ }
++ } else if (ch == "&") {
++ var ok;
++ if (stream.eat("#")) {
++ if (stream.eat("x")) {
++ ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
++ } else {
++ ok = stream.eatWhile(/[\d]/) && stream.eat(";");
++ }
++ } else {
++ ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
++ }
++ return ok ? "atom" : "error";
++ } else {
++ stream.eatWhile(/[^&<]/);
++ return null;
++ }
++ }
++ inText.isInText = true;
++
++ function inTag(stream, state) {
++ var ch = stream.next();
++ if (ch == ">" || (ch == "/" && stream.eat(">"))) {
++ state.tokenize = inText;
++ type = ch == ">" ? "endTag" : "selfcloseTag";
++ return "tag bracket";
++ } else if (ch == "=") {
++ type = "equals";
++ return null;
++ } else if (ch == "<") {
++ state.tokenize = inText;
++ state.state = baseState;
++ state.tagName = state.tagStart = null;
++ var next = state.tokenize(stream, state);
++ return next ? next + " tag error" : "tag error";
++ } else if (/[\'\"]/.test(ch)) {
++ state.tokenize = inAttribute(ch);
++ state.stringStartCol = stream.column();
++ return state.tokenize(stream, state);
++ } else {
++ stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
++ return "word";
++ }
++ }
++
++ function inAttribute(quote) {
++ var closure = function(stream, state) {
++ while (!stream.eol()) {
++ if (stream.next() == quote) {
++ state.tokenize = inTag;
++ break;
++ }
++ }
++ return "string";
++ };
++ closure.isInAttribute = true;
++ return closure;
++ }
++
++ function inBlock(style, terminator) {
++ return function(stream, state) {
++ while (!stream.eol()) {
++ if (stream.match(terminator)) {
++ state.tokenize = inText;
++ break;
++ }
++ stream.next();
++ }
++ return style;
++ }
++ }
++
++ function doctype(depth) {
++ return function(stream, state) {
++ var ch;
++ while ((ch = stream.next()) != null) {
++ if (ch == "<") {
++ state.tokenize = doctype(depth + 1);
++ return state.tokenize(stream, state);
++ } else if (ch == ">") {
++ if (depth == 1) {
++ state.tokenize = inText;
++ break;
++ } else {
++ state.tokenize = doctype(depth - 1);
++ return state.tokenize(stream, state);
++ }
++ }
++ }
++ return "meta";
++ };
++ }
++
++ function Context(state, tagName, startOfLine) {
++ this.prev = state.context;
++ this.tagName = tagName;
++ this.indent = state.indented;
++ this.startOfLine = startOfLine;
++ if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
++ this.noIndent = true;
++ }
++ function popContext(state) {
++ if (state.context) state.context = state.context.prev;
++ }
++ function maybePopContext(state, nextTagName) {
++ var parentTagName;
++ while (true) {
++ if (!state.context) {
++ return;
++ }
++ parentTagName = state.context.tagName;
++ if (!config.contextGrabbers.hasOwnProperty(parentTagName) ||
++ !config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
++ return;
++ }
++ popContext(state);
++ }
++ }
++
++ function baseState(type, stream, state) {
++ if (type == "openTag") {
++ state.tagStart = stream.column();
++ return tagNameState;
++ } else if (type == "closeTag") {
++ return closeTagNameState;
++ } else {
++ return baseState;
++ }
++ }
++ function tagNameState(type, stream, state) {
++ if (type == "word") {
++ state.tagName = stream.current();
++ setStyle = "tag";
++ return attrState;
++ } else if (config.allowMissingTagName && type == "endTag") {
++ setStyle = "tag bracket";
++ return attrState(type, stream, state);
++ } else {
++ setStyle = "error";
++ return tagNameState;
++ }
++ }
++ function closeTagNameState(type, stream, state) {
++ if (type == "word") {
++ var tagName = stream.current();
++ if (state.context && state.context.tagName != tagName &&
++ config.implicitlyClosed.hasOwnProperty(state.context.tagName))
++ popContext(state);
++ if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {
++ setStyle = "tag";
++ return closeState;
++ } else {
++ setStyle = "tag error";
++ return closeStateErr;
++ }
++ } else if (config.allowMissingTagName && type == "endTag") {
++ setStyle = "tag bracket";
++ return closeState(type, stream, state);
++ } else {
++ setStyle = "error";
++ return closeStateErr;
++ }
++ }
++
++ function closeState(type, _stream, state) {
++ if (type != "endTag") {
++ setStyle = "error";
++ return closeState;
++ }
++ popContext(state);
++ return baseState;
++ }
++ function closeStateErr(type, stream, state) {
++ setStyle = "error";
++ return closeState(type, stream, state);
++ }
++
++ function attrState(type, _stream, state) {
++ if (type == "word") {
++ setStyle = "attribute";
++ return attrEqState;
++ } else if (type == "endTag" || type == "selfcloseTag") {
++ var tagName = state.tagName, tagStart = state.tagStart;
++ state.tagName = state.tagStart = null;
++ if (type == "selfcloseTag" ||
++ config.autoSelfClosers.hasOwnProperty(tagName)) {
++ maybePopContext(state, tagName);
++ } else {
++ maybePopContext(state, tagName);
++ state.context = new Context(state, tagName, tagStart == state.indented);
++ }
++ return baseState;
++ }
++ setStyle = "error";
++ return attrState;
++ }
++ function attrEqState(type, stream, state) {
++ if (type == "equals") return attrValueState;
++ if (!config.allowMissing) setStyle = "error";
++ return attrState(type, stream, state);
++ }
++ function attrValueState(type, stream, state) {
++ if (type == "string") return attrContinuedState;
++ if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;}
++ setStyle = "error";
++ return attrState(type, stream, state);
++ }
++ function attrContinuedState(type, stream, state) {
++ if (type == "string") return attrContinuedState;
++ return attrState(type, stream, state);
++ }
++
++ return {
++ startState: function(baseIndent) {
++ var state = {tokenize: inText,
++ state: baseState,
++ indented: baseIndent || 0,
++ tagName: null, tagStart: null,
++ context: null}
++ if (baseIndent != null) state.baseIndent = baseIndent
++ return state
++ },
++
++ token: function(stream, state) {
++ if (!state.tagName && stream.sol())
++ state.indented = stream.indentation();
++
++ if (stream.eatSpace()) return null;
++ type = null;
++ var style = state.tokenize(stream, state);
++ if ((style || type) && style != "comment") {
++ setStyle = null;
++ state.state = state.state(type || style, stream, state);
++ if (setStyle)
++ style = setStyle == "error" ? style + " error" : setStyle;
++ }
++ return style;
++ },
++
++ indent: function(state, textAfter, fullLine) {
++ var context = state.context;
++ // Indent multi-line strings (e.g. css).
++ if (state.tokenize.isInAttribute) {
++ if (state.tagStart == state.indented)
++ return state.stringStartCol + 1;
++ else
++ return state.indented + indentUnit;
++ }
++ if (context && context.noIndent) return CodeMirror.Pass;
++ if (state.tokenize != inTag && state.tokenize != inText)
++ return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
++ // Indent the starts of attribute names.
++ if (state.tagName) {
++ if (config.multilineTagIndentPastTag !== false)
++ return state.tagStart + state.tagName.length + 2;
++ else
++ return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1);
++ }
++ if (config.alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
++ var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter);
++ if (tagAfter && tagAfter[1]) { // Closing tag spotted
++ while (context) {
++ if (context.tagName == tagAfter[2]) {
++ context = context.prev;
++ break;
++ } else if (config.implicitlyClosed.hasOwnProperty(context.tagName)) {
++ context = context.prev;
++ } else {
++ break;
++ }
++ }
++ } else if (tagAfter) { // Opening tag spotted
++ while (context) {
++ var grabbers = config.contextGrabbers[context.tagName];
++ if (grabbers && grabbers.hasOwnProperty(tagAfter[2]))
++ context = context.prev;
++ else
++ break;
++ }
++ }
++ while (context && context.prev && !context.startOfLine)
++ context = context.prev;
++ if (context) return context.indent + indentUnit;
++ else return state.baseIndent || 0;
++ },
++
++ electricInput: /<\/[\s\w:]+>$/,
++ blockCommentStart: "<!--",
++ blockCommentEnd: "-->",
++
++ configuration: config.htmlMode ? "html" : "xml",
++ helperType: config.htmlMode ? "html" : "xml",
++
++ skipAttribute: function(state) {
++ if (state.state == attrValueState)
++ state.state = attrState
++ },
++
++ xmlCurrentTag: function(state) {
++ return state.tagName ? {name: state.tagName, close: state.type == "closeTag"} : null
++ },
++
++ xmlCurrentContext: function(state) {
++ var context = []
++ for (var cx = state.context; cx; cx = cx.prev)
++ if (cx.tagName) context.push(cx.tagName)
++ return context.reverse()
++ }
++ };
++});
++
++CodeMirror.defineMIME("text/xml", "xml");
++CodeMirror.defineMIME("application/xml", "xml");
++if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
++ CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
++
++});
+diff --git a/app/lib/widget/Common.js b/app/lib/widget/Common.js
+index c8b208c..4b322f0 100644
+--- a/app/lib/widget/Common.js
++++ b/app/lib/widget/Common.js
+@@ -597,6 +597,13 @@ widget.reloadDesktopFont = function() {
+ }
+ document.body.style.fontSize = size;
+ }
++
++ var family = Config.get(Config.UI_CUSTOM_FONT_FAMILY);
++ if (family) document.body.style.fontFamily = family;
++
++ var size = Config.get(Config.UI_CUSTOM_FONT_SIZE);
++ if (size) document.body.style.fontSize = size;
++
+ resolve(config);
+ });
+ });
+diff --git a/app/lib/widget/Tree.js b/app/lib/widget/Tree.js
+index 266bf36..f1658d7 100644
+--- a/app/lib/widget/Tree.js
++++ b/app/lib/widget/Tree.js
+@@ -90,6 +90,8 @@ widget.Tree = function() {
+ if (!target || !Dom.hasClass(target, "Checkbox") || target.nodeName.toLowerCase() != "input") return;
+ if (target.disabled) return;
+
++ if (event.which == 2) target.checked = !target.checked;
++
+ var treeContainer = Dom.findUpward(target, {
+ eval: function(n) {
+ return n._tree;
+@@ -102,7 +104,7 @@ widget.Tree = function() {
+ if ((target.checked && tree.options.propagateCheckActionDownwards)
+ || (!target.checked && tree.options.propagateUncheckActionDownwards)) {
+ var itemNode = target.parentNode.parentNode;
+- setItemsCheckedRecursivelyFromNodes(getChildrenContainerFromItemNode(itemNode).childNodes, target.checked);
++ if (event.which != 2) setItemsCheckedRecursivelyFromNodes(getChildrenContainerFromItemNode(itemNode).childNodes, target.checked);
+ }
+
+ Dom.emitEvent("blur", treeContainer, {});
+@@ -189,13 +191,13 @@ widget.Tree = function() {
+ this.ensureNodeExpanded(itemNode);
+ }
+ };
+-
++
+ /*
+ Tree.prototype.sort = function(item) {
+ if (item.sort) return;
+ if (item.children && item.children.length > 0) {
+ item.children.sort(sortNameByAsc);
+-
++
+ for (var i = 0; i < item.children.length; i++) {
+ if (item.children[i].children && item.children[i].children.length > 0) {
+ this.sort(item.children[i]);
+@@ -204,11 +206,11 @@ widget.Tree = function() {
+ }
+ };
+ */
+-
++
+ Tree.prototype.loadChildren = function(parentItem, childrenContainer, callback) {
+ Dom.addClass(childrenContainer, "Loading");
+ var thiz = this;
+-
++
+ this.source(parentItem, function(children) {
+ childrenContainer.innerHTML = "";
+ if (!children) children = [];
+@@ -456,7 +458,7 @@ widget.Tree = function() {
+ this.listener = listener;
+ return this;
+ };
+-
++
+ Tree.prototype.findParentItem = function (contextNode) {
+ var thiz = this;
+ var node = Dom.findUpward(contextNode, {
+@@ -464,15 +466,15 @@ widget.Tree = function() {
+ return n._item || n == thiz.container;
+ }
+ });
+-
++
+ if (!node || !node._item) return null;
+-
++
+ node = Dom.findUpward(node.parentNode, {
+ eval: function (n) {
+ return n._item || n == thiz.container;
+ }
+ });
+-
++
+ if (node && node._item) return node._item;
+ return null;
+ };
+diff --git a/app/package-lock.json b/app/package-lock.json
+index f002744..d5c1306 100644
+--- a/app/package-lock.json
++++ b/app/package-lock.json
+@@ -1,6 +1,6 @@
+ {
+ "name": "Pencil",
+- "version": "3.0.5",
++ "version": "3.1.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+@@ -3235,6 +3235,29 @@
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
+ "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE="
++ },
++ "tar": {
++ "version": "2.2.2",
++ "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
++ "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
++ "requires": {
++ "block-stream": "*",
++ "fstream": "^1.0.12",
++ "inherits": "2"
++ },
++ "dependencies": {
++ "fstream": {
++ "version": "1.0.12",
++ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
++ "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
++ "requires": {
++ "graceful-fs": "^4.1.2",
++ "inherits": "~2.0.0",
++ "mkdirp": ">=0.5 0",
++ "rimraf": "2"
++ }
++ }
++ }
+ }
+ }
+ },
+diff --git a/app/pencil-core/canvasHelper/GestureHelper.js b/app/pencil-core/canvasHelper/GestureHelper.js
+new file mode 100644
+index 0000000..cba1dd0
+--- /dev/null
++++ b/app/pencil-core/canvasHelper/GestureHelper.js
+@@ -0,0 +1,86 @@
++function GestureHelper(canvas) {
++ this.canvas = canvas;
++ this.canvas.gestureHelper = this;
++
++ this.init();
++}
++
++GestureHelper.prototype.init = function () {
++ var thiz = this;
++ this.heldKeyCodes = [];
++
++ this.canvas.focusableBox.addEventListener("keydown", function (event) {
++ if (thiz.heldKeyCodes.indexOf(event.keyCode) >= 0) return;
++
++ thiz.heldKeyCodes.push(event.keyCode);
++ thiz.updateKeyCodes();
++ }, false);
++
++ this.canvas.focusableBox.addEventListener("keyup", function (event) {
++ var index = thiz.heldKeyCodes.indexOf(event.keyCode);
++ if (index < 0) return;
++
++ thiz.heldKeyCodes.splice(index, 1);
++ thiz.updateKeyCodes();
++ }, false);
++
++ this.canvas.focusableBox.addEventListener("blur", function (event) {
++ thiz.heldKeyCodes.length = 0;
++ thiz.updateKeyCodes();
++ }, false);
++
++ // sample registry
++ this.gestureRegistry = {
++ keys: {
++ "R": {
++ type: "Shape",
++ defId: "dgthanhan.MaterialDesktopMockup:rectangle"
++ }
++ }
++ };
++};
++
++GestureHelper.prototype.handleMouseDown = function (event) {
++ if (!this.activeGestureDef) return false;
++ if (this.activeGestureDef.type == "Shape") {
++ var def = CollectionManager.shapeDefinition.locateDefinition(this.activeGestureDef.defId);
++ if (!def) return;
++
++ var loc = this.canvas.getEventLocation(event);
++ this.canvas.insertShape(def);
++
++ var controller = this.canvas.currentController;
++ var bbox = controller.getBoundingRect();
++ controller.moveBy(loc.x, loc.y, true);
++ controller.setProperty("box", new Dimension(0, 0));
++
++ this.canvas.selectShape(controller.svg);
++ window.setTimeout(function () {
++ this.canvas.geometryEditor.handleMouseDown(event);
++ this.canvas.geometryEditor.currentAnchor = this.canvas.geometryEditor.anchor4;
++ }.bind(this), 10);
++
++ return true;
++ }
++
++ return false;
++};
++GestureHelper.prototype.handleMouseUp = function (event) {
++ return false;
++};
++GestureHelper.prototype.updateKeyCodes = function () {
++ if (!GestureHelper._output) {
++ GestureHelper._output = document.createElement("div");
++ ApplicationPane._instance.contentHeader.appendChild(GestureHelper._output);
++ }
++
++ this.activeGestureDef = null;
++
++ for (var code of this.heldKeyCodes) {
++ var c = String.fromCharCode(code);
++ this.activeGestureDef = this.gestureRegistry.keys[c];
++ if (this.activeGestureDef) break;
++ }
++
++ GestureHelper._output.innerHTML = this.heldKeyCodes.join(", ");
++};
+diff --git a/app/pencil-core/canvasHelper/snappingHelper.js b/app/pencil-core/canvasHelper/snappingHelper.js
+index 5d2f2f8..fff47ad 100644
+--- a/app/pencil-core/canvasHelper/snappingHelper.js
++++ b/app/pencil-core/canvasHelper/snappingHelper.js
+@@ -4,8 +4,6 @@ function SnappingHelper(canvas) {
+ Config.set("object.snapping.enabled", true);
+ }
+ this.init();
+-
+- //var this = new SnappingHelper
+ };
+ SnappingHelper.prototype.isGridSnappingEnabled = function () {
+ return Config.get("edit.snap.grid") == true;
+@@ -113,20 +111,20 @@ SnappingHelper.prototype.rebuildSnappingGuide = function () {
+ }
+
+ var margin = (Pencil.controller && !this.canvas.options.ignorePageMarging) ? Pencil.controller.getDocumentPageMargin() : 0;
+- if (margin) {
+- var uid = Util.newUUID();
+- this.snappingGuide[uid] = {
+- vertical: [
+- new SnappingData("MarginSnap", margin, "Left", true, uid),
+- new SnappingData("MarginSnap", this.canvas.width - margin, "Right", true, uid)
+- ],
+- horizontal: [
+- new SnappingData("MarginSnap", margin, "Top", false, uid),
+- new SnappingData("MarginSnap", this.canvas.height - margin, "Bottom", false, uid)
+- ]
+- };
+-
+- }
++ margin = parseInt(margin, 10);
++ if (isNaN(margin) || margin < 0) margin = 0;
++ var uid = Util.newUUID();
++ var snap = {
++ vertical: [
++ new SnappingData("MarginSnap", margin, "Left", true, uid),
++ new SnappingData("MarginSnap", this.canvas.width - margin, "Right", true, uid)
++ ],
++ horizontal: [
++ new SnappingData("MarginSnap", margin, "Top", false, uid),
++ new SnappingData("MarginSnap", this.canvas.height - margin, "Bottom", false, uid)
++ ]
++ };
++ this.snappingGuide[uid] = snap;
+
+ this.sortData();
+ };
+@@ -148,6 +146,9 @@ SnappingHelper.prototype.updateSnappingGuide = function (controller, remove) {
+ this.sortData();
+ }
+ };
++SnappingHelper.prototype.onControllerSnapshot = function (controller) {
++ this.lastSnappingGuideSnapshot = (controller && controller.getSnappingGuide) ? controller.getSnappingGuide() : null;
++};
+ SnappingHelper.prototype.sortData = function () {
+ var x = [];
+ var y = [];
+@@ -162,106 +163,103 @@ SnappingHelper.prototype.sortData = function () {
+ this.snappedX = false;
+ this.snappedY = false;
+ };
+-SnappingHelper.prototype.findSnapping = function (drawX, drawY, ghost, snap, shift, grid) {
+- if (!this.isSnappingEnabled()) return;
+- try {
+- if (drawX && !grid) {
+- this.clearSnappingGuideX();
+- }
+- if (drawY && !grid) {
+- this.clearSnappingGuideY();
+- }
+-
+- //debug("start ***");
+- if (!ghost && (!this.canvas.controllerHeld || !this.canvas.currentController || !this.canvas.currentController.getSnappingGuide)) return null;
+-
+- var _snap = snap ? snap : Pencil.SNAP;
+- if (shift) {
+- _snap = 1;
+- }
+-
+- var b = !ghost ? this.canvas.currentController.getSnappingGuide() : ghost;
++SnappingHelper.prototype.applySnapping = function (dx, dy, controller) {
++ if (!this.isSnappingEnabled()) return null;
++ if (!this.lastSnappingGuideSnapshot) return null;
++ var currentControllerId = controller.id;
+
+- var snappingData = this.findSnappingImpl(this.canvas.currentController, b, _snap, grid);
+- var currentDx = Pencil.SNAP + 10;
+- var currentDy = Pencil.SNAP + 10;
+- if (grid) {
+- currentDx = Pencil.getGridSize().w;
+- currentDy = Pencil.getGridSize().h;
+- }
++ var xsnap = this.applySnappingValue(dx, this.lastSnappingGuideSnapshot.vertical, this.lastXData, controller);
++ var ysnap = this.applySnappingValue(dy, this.lastSnappingGuideSnapshot.horizontal, this.lastYData, controller);
+
+- var snapDelta = {
+- dx: 0, dy: 0
+- };
++ this.drawSnaps(xsnap, ysnap);
+
+- if (snappingData.bestVertical) {
+- if (Math.abs(snappingData.bestVertical.dx) < Math.abs(currentDx)) {
+- currentDx = snappingData.bestVertical.dx;
+- }
++ return {
++ xsnap: xsnap,
++ ysnap: ysnap
++ };
++};
++SnappingHelper.prototype.drawSnaps = function (xsnap, ysnap) {
++ var thiz = this;
+
+- if (Math.abs(currentDx) < _snap) {
+- if (drawX && !grid) {
+- for (var l = 0; l < snappingData.verticals.length; l++) {
+- var verticalGuide = document.createElementNS(PencilNamespaces.svg, "line");
++ this.clearSnappingGuideX();
++ if (xsnap && xsnap.matchingGuides) {
++ xsnap.matchingGuides.forEach(function (guide) {
++ var verticalGuide = document.createElementNS(PencilNamespaces.svg, "line");
+
+- verticalGuide.setAttribute("class", snappingData.verticals[l].x.type);
+- verticalGuide.setAttribute("x1", Math.round(snappingData.verticals[l].x.pos) * this.canvas.zoom);
+- verticalGuide.setAttribute("y1", 0);
+- verticalGuide.setAttribute("x2", Math.round(snappingData.verticals[l].x.pos) * this.canvas.zoom);
+- verticalGuide.setAttribute("y2", this.canvas.height * this.canvas.zoom);
++ verticalGuide.setAttribute("class", guide.type);
++ verticalGuide.setAttribute("x1", Math.round(guide.pos * thiz.canvas.zoom));
++ verticalGuide.setAttribute("y1", 0);
++ verticalGuide.setAttribute("x2", Math.round(guide.pos * thiz.canvas.zoom));
++ verticalGuide.setAttribute("y2", Math.round(thiz.canvas.height * thiz.canvas.zoom));
+
+- this.snappingGuideContainerX.appendChild(verticalGuide);
++ thiz.snappingGuideContainerX.appendChild(verticalGuide);
+
+- this._snappingGuideContainerXEmpty = false;
+- }
+- }
+- if (grid) {
+- this.unsnapX = (Pencil.getGridSize().w / 2) + 3;
+- } else {
+- this.unsnapX = Pencil.UNSNAP;
+- }
+- snapDelta.dx = currentDx;
+- }
+- }
++ thiz._snappingGuideContainerXEmpty = false;
++ });
++ }
+
+- if (snappingData.bestHorizontal) {
+- if (Math.abs(snappingData.bestHorizontal.dy) < Math.abs(currentDy)) {
+- currentDy = snappingData.bestHorizontal.dy;
+- }
+- if (Math.abs(currentDy) < _snap) {
+- if (drawY && !grid) {
+- for (var l = 0; l < snappingData.horizontals.length; l++) {
+- var horizontalGuide = document.createElementNS(PencilNamespaces.svg, "line");
++ this.clearSnappingGuideY();
++ if (ysnap && ysnap.matchingGuides) {
++ ysnap.matchingGuides.forEach(function (guide) {
++ var horizontalGuide = document.createElementNS(PencilNamespaces.svg, "line");
+
+- horizontalGuide.setAttribute("class", snappingData.horizontals[l].y.type);
+- horizontalGuide.setAttribute("x1", 0 * this.canvas.zoom);
+- horizontalGuide.setAttribute("y1", Math.round(snappingData.horizontals[l].y.pos) * this.canvas.zoom);
+- horizontalGuide.setAttribute("x2", this.canvas.width * this.canvas.zoom);
+- horizontalGuide.setAttribute("y2", Math.round(snappingData.horizontals[l].y.pos) * this.canvas.zoom);
++ horizontalGuide.setAttribute("class", guide.type);
++ horizontalGuide.setAttribute("x1", 0);
++ horizontalGuide.setAttribute("y1", Math.round(guide.pos * thiz.canvas.zoom));
++ horizontalGuide.setAttribute("x2", Math.round(thiz.canvas.width * thiz.canvas.zoom));
++ horizontalGuide.setAttribute("y2", Math.round(guide.pos * thiz.canvas.zoom));
+
+- this.snappingGuideContainerY.appendChild(horizontalGuide);
++ thiz.snappingGuideContainerY.appendChild(horizontalGuide);
+
+- this._snappingGuideContainerYEmpty = false;
+- }
+- }
+- if (grid) {
+- this.unsnapY = (Pencil.getGridSize().h / 2) + 3;
+- } else {
+- this.unsnapY = Pencil.UNSNAP;
+- }
+- snapDelta.dy = currentDy;
++ thiz._snappingGuideContainerYEmpty = false;
++ });
++ }
++};
++SnappingHelper.prototype.applySnappingValue = function (d, controllerPositions, canvasPositions, controller) {
++ var currentControllerId = controller.id;
++ var closestDistance = Number.MAX_VALUE;
++ var closestDelta = undefined;
++ var closestGuide = undefined;
++ var matchingGuides = [];
++ canvasPositions.forEach(function (canvasGuide) {
++ if (!canvasGuide || canvasGuide.id == currentControllerId || canvasGuide.disabled) return;
++ if (controller.containsControllerId && controller.containsControllerId(canvasGuide.id)) return;
++
++ controllerPositions.forEach(function (controllerGuide) {
++ if (!controllerGuide || controllerGuide.disabled) return;
++
++ var delta = canvasGuide.pos - (controllerGuide.pos + d);
++ var distance = Math.abs(delta);
++ if (distance < closestDistance) {
++ closestDistance = distance;
++ closestDelta = delta;
++ closestGuide = canvasGuide;
++ matchingGuides = [canvasGuide];
++ } else if (delta == closestDelta) {
++ matchingGuides.push(canvasGuide);
+ }
+- }
++ });
++ });
+
+- //debug("end ***");
+- return snapDelta;
+- } catch(e) {
+- error(e);
++ if (closestDistance <= Pencil.SNAP) {
++ return {
++ d: d + closestDelta,
++ matchingGuides: matchingGuides
++ }
++ } else {
++ return null;
+ }
+-
+- return null;
+ };
++
+ SnappingHelper.prototype.sort = function(d) {
++ // var d2 = [];
++ // var positions = [];
++ // d.forEach(function (v) {
++ // if (positions.indexOf(v.pos) >= 0) return;
++ // d2.push(v);
++ // positions.push(v.pos);
++ // });
++ // d = d2;
+ for (var i = 0; i < d.length - 1; i++) {
+ for (var j = i + 1; j < d.length; j++) {
+ if (d[j].pos < d[i].pos) {
+@@ -284,106 +282,3 @@ SnappingHelper.prototype.allowSnapping = function (v, x) {
+
+ return false;
+ };
+-SnappingHelper.prototype.findSnappingImpl = function(controller, ghost, snap, grid) {
+-
+- try {
+- var c = !ghost ? controller.getSnappingGuide() : ghost;
+- var currentControllerId = controller.id;
+-
+- var verticals = [];
+- var horizontals = [];
+-
+- var x = this.lastXData;
+- var y = this.lastYData;
+-
+- var _minsnap = snap ? snap : Pencil.SNAP;
+-
+- var bestV = null;
+- for (var v = 0; v < c.vertical.length; v++) {
+- var vertical = { };
+- for (var i = 0; i < x.length; i++) {
+- if (!x[i].disabled
+- && !c.vertical[v].disabled
+- && x[i].id != currentControllerId
+- && (!controller.containsControllerId || !controller.containsControllerId(x[i].id))
+- && this.allowSnapping(c.vertical[v], x[i])) {
+- if ((grid && x[i].type == "GridSnap" && Math.abs(x[i].pos - c.vertical[v].pos) <= _minsnap)
+- || (!grid && x[i].type != "GridSnap" && Math.abs(x[i].pos - c.vertical[v].pos) <= _minsnap)) {
+-
+- _minsnap = Math.abs(x[i].pos - c.vertical[v].pos);
+- vertical.x = x[i];
+- vertical.dx = x[i].pos - c.vertical[v].pos;
+-
+- if (!bestV || Math.abs(bestV.dx > vertical.dx)) {
+- bestV = vertical;
+- }
+- }
+- }
+- }
+- if (vertical.x) {
+- verticals.push(vertical);
+- }
+- }
+-
+- //debug("found: " + verticals.length);
+- //for (var v = 0; v < verticals.length; v++) {
+- // debug(verticals[v].toSource());
+- //}
+-
+- // if (verticals.length > 0) {
+- // while (Math.abs(verticals[0].dx) != Math.abs(verticals[verticals.length - 1].dx)) {
+- // //debug("delete");
+- // verticals.splice(0, 1);
+- // }
+- // }
+-
+- var _minsnap = snap ? snap : Pencil.SNAP;
+-
+- var bestH = null;
+- for (var v = 0; v < c.horizontal.length; v++) {
+- var horizontal = { };
+- for (var i = 0; i < y.length; i++) {
+- if (!y[i].disabled
+- && !c.horizontal[v].disabled
+- && y[i].id != currentControllerId
+- && (!controller.containsControllerId || !controller.containsControllerId(y[i].id))
+- && this.allowSnapping(c.horizontal[v], y[i])) {
+- if ((grid && y[i].type == "GridSnap" && Math.abs(y[i].pos - c.horizontal[v].pos) <= _minsnap)
+- || (!grid && y[i].type != "GridSnap" && Math.abs(y[i].pos - c.horizontal[v].pos) <= _minsnap)) {
+-
+- _minsnap = Math.abs(y[i].pos - c.horizontal[v].pos);
+- horizontal.y = y[i];
+- horizontal.dy = y[i].pos - c.horizontal[v].pos;
+-
+- if (!bestH || Math.abs(bestH.dy > horizontal.dy)) {
+- bestH = horizontal;
+- }
+- }
+- }
+- }
+- if (horizontal.y) {
+- horizontals.push(horizontal);
+- }
+- }
+-
+- //debug("found: " + horizontals.length);
+- //for (var v = 0; v < horizontals.length; v++) {
+- // debug(horizontals[v].toSource());
+- //}
+-
+- // if (horizontals.length > 0) {
+- // while (Math.abs(horizontals[0].dy) != Math.abs(horizontals[horizontals.length - 1].dy)) {
+- // horizontals.splice(0, 1);
+- // }
+- // }
+-
+- return {
+- verticals: verticals.sort(function (a, b) { return Math.abs(a.dx) - Math.abs(b.dx)}),
+- horizontals: horizontals.sort(function (a, b) { return Math.abs(a.dy) - Math.abs(b.dy)}),
+- bestVertical: bestV,
+- bestHorizontal: bestH
+- };
+- } catch(e) {
+- Console.dumpError(e);
+- }
+-};
+diff --git a/app/pencil-core/clipartBrowser/newOpenClipartSearch.js b/app/pencil-core/clipartBrowser/newOpenClipartSearch.js
+index 57f317c..7f5fe1b 100644
+--- a/app/pencil-core/clipartBrowser/newOpenClipartSearch.js
++++ b/app/pencil-core/clipartBrowser/newOpenClipartSearch.js
+@@ -1,275 +1,80 @@
+
+ function OpenClipartSearch2() {
+- this.title = "OpenClipart.org (API)";
+- this.name = "OpenClipart.org (API)";
++ this.title = "OpenClipart.org (cchost)";
++ this.name = "OpenClipart.org (cchost)";
+ this.uri = "http://openclipart.org/";
+
+ this.icon = "data:image/png;base64," +
+- "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" +
+- "OcJp9XCvYoAsH/B//9k=";
+-
+-
+- this.baseUri = "http://openclipart.org/api/search/";
++ "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" +
++ "/xX/BwSxr+GgTUkRAAAAAElFTkSuQmCC";
++
++
++ this.baseUri = "http://www.openclipart.org/cchost/media/api/search";
+
+ this.options = {
+- page: 1
++ offset: this.offset,
++ limit: this.limit,
++ f: "js"
+ };
+
+ this.req = [];
+@@ -313,7 +118,7 @@ OpenClipartSearch2.prototype.searchImpl = function(query, options, callback) {
+
+ var thiz = this;
+ debug("OpenClipart: searching '" + query + "'");
+- debug("url: " + url);
++ //debug("url: " + url);
+ WebUtil.get(url, function(response) {
+ var r = thiz.parseSearchResult(response);
+ if (callback) {
+@@ -321,114 +126,25 @@ OpenClipartSearch2.prototype.searchImpl = function(query, options, callback) {
+ }
+ }, this.req);
+ };
++
+ OpenClipartSearch2.prototype.parseSearchResult = function (response) {
+ var r = {result: [], resultCount: 0};
+ if (!response) return r;
+ try {
+- var dom = Dom.parseDocument(response);
+-
+- Dom.workOn("//*/item", dom, function (itemNode) {
+- var item = {
+- name: Dom.getSingleValue("./title/text()", itemNode),
+- des: Dom.getSingleValue("./description/text()", itemNode),
+- src: Dom.getSingleValue("./enclosure/@url", itemNode),
+- type: Dom.getSingleValue("./enclosure/@type", itemNode),
+- length: Dom.getSingleValue("./enclosure/@length", itemNode),
+- thumb: Dom.getSingleValue("./media:thumbnail/@url", itemNode),
+- creator: Dom.getSingleValue("./dc:creator/text()", itemNode),
+- license: Dom.getSingleValue("./rcc:license/text()", itemNode),
+- pubDate: Dom.getSingleValue("./pubDate/text()", itemNode),
+- link: Dom.getSingleValue("./link/text()", itemNode)
+- };
++ // FIXME: should use JSON parser
++ response = JSON.parse(response);
++ r.resultCount = response.length;
++ for (var i = 0; i < response.length; i++) {
++ var item = {name: response[i].upload_name, author: response[i].user_real_name, desc: response[i].upload_description, images: []};
++ for (var j = 0; j < response[i].files.length; j++) {
++ item.images.push({src: response[i].files[j].download_url, type: response[i].files[j].file_format_info.mime_type, typeName: this.formatType(response[i].files[j].file_format_info.mime_type), size: response[i].files[j].file_rawsize});
++ }
+ r.result.push(item);
+- });
++ }
+ } catch (e) {
+- error("error: " + e);
++ Console.dumpError(e);
+ }
+ return r;
+ };
+
+-function OpenClipartHandler() {
+- this.items = [];
+- this.currentItem = null;
+- this.currentState = OpenClipartHandler.STATE_CHANNEL;
+-}
+-
+-OpenClipartHandler.prototype.startDocument = function() {
+-}
+-
+-OpenClipartHandler.prototype.endDocument = function() {
+-}
+-
+-OpenClipartHandler.prototype.startElement = function(name, attribs) {
+- if (!name || name === "") return;
+- if (name === "item") {
+- this.currentState = OpenClipartHandler.STATE_ITEM;
+- this.currentItem = {images: [{type: "image/svg+xml", typeName: "SVG"}]};
+- } else if (name === "title") {
+- if (this.currentState >= OpenClipartHandler.STATE_ITEM) {
+- this.currentState = OpenClipartHandler.STATE_TITLE;
+- }
+- } else if (name === "dc:creator") {
+- if (this.currentState >= OpenClipartHandler.STATE_ITEM) {
+- this.currentState = OpenClipartHandler.STATE_AUTHOR;
+- }
+- } else if (name === "description") {
+- if (this.currentState >= OpenClipartHandler.STATE_ITEM) {
+- this.currentState = OpenClipartHandler.STATE_DESC;
+- }
+- } else if (name === "enclosure") {
+- if (this.currentState >= OpenClipartHandler.STATE_ITEM) {
+- this.currentItem.images[0].src = attribs["url"];
+- this.currentState = OpenClipartHandler.STATE_URL;
+- }
+- } else if (name === "media:thumbnail") {
+- if (this.currentState >= OpenClipartHandler.STATE_ITEM) {
+- this.currentItem.images[0].thumbnail = attribs["url"];
+- this.currentState = OpenClipartHandler.STATE_THUMBNAIL;
+- }
+- }
+-}
+-OpenClipartHandler.prototype.endElement = function(name) {
+- if (!name || name === "") return;
+- if (name === "item") {
+- this.currentState = OpenClipartHandler.STATE_CHANNEL;
+- this.items.push(this.currentItem);
+- } else if (name === "title") {
+- if (this.currentState == OpenClipartHandler.STATE_TITLE) {
+- this.currentItem.name = this.currentText;
+- this.currentState = OpenClipartHandler.STATE_ITEM;
+- }
+- } else if (name === "dc:creator") {
+- if (this.currentState == OpenClipartHandler.STATE_AUTHOR) {
+- this.currentItem.author = this.currentText;
+- this.currentState = OpenClipartHandler.STATE_ITEM;
+- }
+- } else if (name === "description") {
+- if (this.currentState == OpenClipartHandler.STATE_DESC) {
+- this.currentItem.desc = this.currentText;
+- this.currentState = OpenClipartHandler.STATE_ITEM;
+- }
+- } else if (name === "enclosure ") {
+- if (this.currentState == OpenClipartHandler.STATE_URL) {
+- this.currentState = OpenClipartHandler.STATE_ITEM;
+- }
+- } else if (name === "media:thumbnail ") {
+- if (this.currentState == OpenClipartHandler.STATE_THUMBNAIL) {
+- this.currentState = OpenClipartHandler.STATE_ITEM;
+- }
+- }
+-}
+-OpenClipartHandler.prototype.characters = function(data) {
+- this.currentText = data;
+-}
+-
+-OpenClipartHandler.STATE_CHANNEL = 0;
+-OpenClipartHandler.STATE_ITEM = 2;
+-OpenClipartHandler.STATE_TITLE = 21;
+-OpenClipartHandler.STATE_DESC = 22;
+-OpenClipartHandler.STATE_AUTHOR = 23;
+-OpenClipartHandler.STATE_URL = 24;
+-OpenClipartHandler.STATE_THUMBNAIL = 25;
+-
+-// SearchManager.registerSearchEngine(OpenClipartSearch2, true);
++SearchManager.registerSearchEngine(OpenClipartSearch2, true);
+diff --git a/app/pencil-core/common/Canvas.js b/app/pencil-core/common/Canvas.js
+index 95f85bd..12f2995 100644
+--- a/app/pencil-core/common/Canvas.js
++++ b/app/pencil-core/common/Canvas.js
+@@ -171,6 +171,7 @@ function Canvas(element, options, containerScrollPane) {
+ "RangeBound");
+
+ this.snappingHelper = new SnappingHelper(this);
++ new GestureHelper(this);
+
+ this.idSeed = 1;
+
+@@ -960,6 +961,8 @@ Canvas.prototype.handleScrollPane = function(event) {
+ }
+
+ Canvas.prototype.handleMouseUp = function (event) {
++ if (this.gestureHelper && this.gestureHelper.handleMouseUp(event)) return;
++
+ if (this.resizing) {
+ this.commitResize(event);
+ this.isSelectingRange = false;
+@@ -981,7 +984,7 @@ Canvas.prototype.handleMouseUp = function (event) {
+ setter : null
+ });
+ }
+-
++
+ Connector.prepareInvalidation(this);
+
+ if (this.currentController.invalidateOutboundConnections) {
+@@ -990,7 +993,7 @@ Canvas.prototype.handleMouseUp = function (event) {
+ if (this.currentController.invalidateInboundConnections) {
+ this.currentController.invalidateInboundConnections();
+ }
+-
++
+ Connector.finishInvalidation();
+ }
+ if (this.controllerHeld && this.hasMoved) {
+@@ -1005,7 +1008,7 @@ Canvas.prototype.handleMouseUp = function (event) {
+ this.hasMoved = true;
+
+ this.controllerHeld = false;
+-
++
+ if (this.isSelectingRange) {
+ this.setRangeBoundVisibility(false);
+ this.isSelectingRange = false;
+@@ -1156,7 +1159,7 @@ Canvas.prototype.handleResizeMouseMove = function (event) {
+
+ var dw = Math.round((event.clientX - this.resizeInfo.ox) / this.zoom);
+ var dh = Math.round((event.clientY - this.resizeInfo.oy) / this.zoom);
+-
++
+ if (event.shiftKey) dw = 0;
+
+ var newW = this.resizeInfo.ow + dw;
+@@ -1277,7 +1280,7 @@ Canvas.prototype.handleMouseMove = function (event, fake) {
+
+ var dx = newX - this.oX;
+ var dy = newY - this.oY;
+-
++
+ //direction ratios
+ var hdr = event.ctrlKey && Math.abs(dx) < Math.abs(dy) ? 0 : 1;
+ var vdr = event.ctrlKey && Math.abs(dx) >= Math.abs(dy) ? 0 : 1;
+@@ -1297,92 +1300,17 @@ Canvas.prototype.handleMouseMove = function (event, fake) {
+
+ this.hasMoved = true;
+
+- var gridSize = Pencil.getGridSize();
+- var snap = null;
+- if (Config.get("object.snapping.enabled", true) == true) {
+- snap = this.snappingHelper.findSnapping(accX
+- && !this.snappingHelper.snappedX, accY
+- && !this.snappingHelper.snappedY, null, null,
+- event.shiftKey);
+- }
+- if (Config.get("edit.snap.grid", false) == true) {
+- var snapGrid = this.snappingHelper.findSnapping(accX
+- && !this.snappingHelper.snappedX, accY
+- && !this.snappingHelper.snappedY, null, gridSize.w / 2,
+- event.shiftKey, true);
+- if (snap && snapGrid) {
+- if (snap.dx == 0) {
+- snap.dx = snapGrid.dx;
+- }
+- if (snap.dy == 0) {
+- snap.dy = snapGrid.dy;
+- }
+- } else {
+- snap = snapGrid;
+- }
+- // debug("snap grid: " + [snapGrid.dx, snapGrid.dy]);
+- }
+- // debug("snap: " + [snap.dx, snap.dy, this.snappedX,
+- // this.snappedY]);
+- if (!event.shiftKey
+- && snap
+- && ((snap.dx != 0 && !this.snappingHelper.snappedX && accX)
+- || (snap.dy != 0 && !this.snappingHelper.snappedY && accY)
+- )) {
+- if (snap.dx != 0 && !this.snappingHelper.snappedX) {
+- this.snappingHelper.snappedX = true;
+- this.snappingHelper.snapX = newX;
+- this.currentController._pSnapshot.lastDX += snap.dx;
+- // debug("snapX");
+- }
+- if (snap.dy != 0 && !this.snappingHelper.snappedY) {
+- this.snappingHelper.snappedY = true;
+- this.snappingHelper.snapY = newY;
+- this.currentController._pSnapshot.lastDY += snap.dy;
+- // debug("snapY");
+- }
+- this.currentController.moveBy(snap.dx * hdr, snap.dy * vdr);
+- } else {
+- var unsnapX = event.shiftKey
+- || (this.snappingHelper.snapX != 0 && (Math
+- .abs(this.snappingHelper.snapX - newX) > this.snappingHelper.unsnapX));
+- var unsnapY = event.shiftKey
+- || (this.snappingHelper.snapY != 0 && (Math
+- .abs(this.snappingHelper.snapY - newY) > this.snappingHelper.unsnapY));
+- // debug("unsnap: " + [unsnapX, unsnapY]);
+-
+- if (!this.snappingHelper.snappedX
+- && !this.snappingHelper.snappedY) {
+- this.currentController.moveFromSnapshot(dx * hdr, dy * vdr);
+- } else {
+- if (unsnapX || !this.snappingHelper.snappedX) {
+- this.currentController
+- .moveFromSnapshot(
+- dx * hdr,
+- this.snappingHelper.snappedY ? this.currentController._pSnapshot.lastDY * vdr
+- : dy * vdr);
+- }
+- if (unsnapY || !this.snappingHelper.snappedY) {
+- this.currentController
+- .moveFromSnapshot(
+- this.snappingHelper.snappedX ? this.currentController._pSnapshot.lastDX * hdr
+- : dx * hdr, dy * vdr);
+- this.snappingHelper.snapY = 0;
+- this.snappingHelper.snappedY = false;
+- }
+- if (unsnapX || !this.snappingHelper.snappedX) {
+- this.snappingHelper.snapX = 0;
+- this.snappingHelper.snappedX = false;
+- }
+-
+- if (unsnapX) {
+- this.snappingHelper.clearSnappingGuideX();
+- }
+- if (unsnapY) {
+- this.snappingHelper.clearSnappingGuideY();
+- }
+- }
++ dx = dx * hdr;
++ dy = dy * vdr;
++
++ if (!event.shiftKey) {
++ var snapResult = this.snappingHelper.applySnapping(dx, dy, this.currentController);
++ if (snapResult && snapResult.xsnap) dx = snapResult.xsnap.d;
++ if (snapResult && snapResult.ysnap) dy = snapResult.ysnap.d;
+ }
++
++ this.currentController.moveFromSnapshot(dx, dy);
++
+ if (this.currentController.dockingManager) {
+ this.currentController.dockingManager.altKey = event.altKey;
+ }
+@@ -1474,7 +1402,7 @@ Canvas.prototype.handleKeyPress = function (event) {
+ this.run(function () {
+ // this.currentController.moveBy(dx, dy);
+ this.currentController.moveBy(dx, dy, false, true);
+-
++
+ Connector.prepareInvalidation(this);
+ if (this.currentController.invalidateOutboundConnections) {
+ this.currentController.invalidateOutboundConnections();
+@@ -1925,7 +1853,7 @@ Canvas.prototype.invalidateEditors = function (source) {
+ e.invalidate();
+ }
+
+- // Pencil.invalidateSharedEditor();
++ Pencil.invalidateSharedEditor();
+ // invalidates all selections
+ for (var i = 0; i < this.selectionContainer.childNodes.length; i++) {
+ var rect = this.selectionContainer.childNodes[i];
+@@ -2115,7 +2043,7 @@ Canvas.prototype.doPaste = function (withAlternative) {
+ if (image) {
+ var id = Pencil.controller.nativeImageToRefSync(image);
+ var size = image.getSize();
+-
++
+ contents.push({
+ type: PNGImageXferHelper.MIME_TYPE,
+ data: new ImageData(size.width, size.height, ImageData.idToRefString(id))
+@@ -2154,6 +2082,8 @@ Canvas.prototype.handleMouseDown = function (event) {
+ tick("begin");
+ Dom.emitEvent("p:CanvasMouseDown", this.element, {});
+
++ if (this.gestureHelper && this.gestureHelper.handleMouseDown(event)) return;
++
+ var canvasList = Pencil.getCanvasList();
+ for (var i = 0; i < canvasList.length; i++) {
+ if (canvasList[i] != this) {
+@@ -2175,9 +2105,9 @@ Canvas.prototype.handleMouseDown = function (event) {
+ return node.hasAttributeNS
+ && node.hasAttributeNS(PencilNamespaces.p, "type");
+ });
+-
++
+ if (top && this.isShapeLocked(top)) top = null;
+-
++
+ if (!top) {
+ this.lastTop = null;
+ // this.clearSelection();
+@@ -2196,7 +2126,7 @@ Canvas.prototype.handleMouseDown = function (event) {
+ width : 0,
+ height : 0
+ };
+-
++
+ this._sayTargetChanged();
+ this.endFormatPainter();
+
+@@ -2289,6 +2219,7 @@ Canvas.prototype.handleMouseDown = function (event) {
+
+ tick("before setPositionSnapshot");
+ thiz.currentController.setPositionSnapshot();
++ thiz.snappingHelper.onControllerSnapshot(this.currentController);
+ tick("after setPositionSnapshot");
+
+ thiz.duplicateFunc = null;
+@@ -2332,6 +2263,7 @@ Canvas.prototype.handleMouseDown = function (event) {
+
+ tick("before setPositionSnapshot");
+ this.currentController.setPositionSnapshot();
++ this.snappingHelper.onControllerSnapshot(this.currentController);
+ tick("after setPositionSnapshot");
+
+ if (event.button == 0)
+@@ -2923,6 +2855,7 @@ Canvas.prototype.startFakeMove = function (event) {
+ this.oldPos = this.currentController.getGeometry();
+
+ this.currentController.setPositionSnapshot();
++ this.snappingHelper.onControllerSnapshot(this.currentController);
+
+ // OnScreenTextEditor._hide();
+
+@@ -2979,6 +2912,9 @@ Canvas.prototype.__dragenter = function (event) {
+ };
+ Canvas.prototype.__dragleave = function (event) {
+ // this.element.removeAttribute("p:selection");
++ this.element.removeAttribute("is-dragover");
++ this.element.removeAttribute("p:holding");
++
+ if (!this.currentDragObserver)
+ return;
+ try {
+@@ -2986,8 +2922,6 @@ Canvas.prototype.__dragleave = function (event) {
+ } catch (e) {
+ Console.dumpError(e);
+ }
+- this.element.removeAttribute("is-dragover");
+- this.element.removeAttribute("p:holding");
+ };
+ Canvas.prototype.__dragend = function (event) {
+ this.element.removeAttribute("is-dragover");
+diff --git a/app/pencil-core/common/DocumentHandler.js b/app/pencil-core/common/DocumentHandler.js
+index 747206b..8cb7b9c 100644
+--- a/app/pencil-core/common/DocumentHandler.js
++++ b/app/pencil-core/common/DocumentHandler.js
+@@ -46,11 +46,11 @@ DocumentHandler.prototype.openDocument = function (callback) {
+ filters: [
+ { name: "Pencil Documents", extensions: thiz.getAllSupportedExtensions(false) }
+ ]
+- }, function (filenames) {
+- if (!filenames || filenames.length <= 0) return;
+- Config.set("document.open.recentlyDirPath", path.dirname(filenames[0]));
++ }).then(function (res) {
++ if (!res || !res.filePaths || res.filePaths.length <= 0) return;
++ Config.set("document.open.recentlyDirPath", path.dirname(res.filePaths[0]));
+
+- thiz.loadDocument(filenames[0], callback);
++ thiz.loadDocument(res.filePaths[0], callback);
+
+ });
+ };
+@@ -146,8 +146,9 @@ DocumentHandler.prototype.pickupTargetFileToSave = function (callback) {
+ title: "Save as",
+ defaultPath: defaultPath,
+ filters: filters
+- }, function (filePath) {
+- if (filePath) {
++ }).then(function (res) {
++ if (res && res.filePath) {
++ var filePath = res.filePath;
+ var ext = path.extname(filePath);
+ if (ext != defaultFileType && fs.existsSync(filePath)) {
+ Dialog.confirm("Are you sure you want to overwrite the existing file?", filePath,
+@@ -166,7 +167,7 @@ DocumentHandler.prototype.pickupTargetFileToSave = function (callback) {
+ Config.set("document.save.recentlyDirPath", path.dirname(filePath));
+ }
+ if (callback) {
+- callback(filePath);
++ callback(res.filePath);
+ }
+ });
+ };
+diff --git a/app/pencil-core/common/FontLoader.js b/app/pencil-core/common/FontLoader.js
+index 11efd5d..0d566fa 100644
+--- a/app/pencil-core/common/FontLoader.js
++++ b/app/pencil-core/common/FontLoader.js
+@@ -151,7 +151,7 @@ FontLoader.prototype.embedToDocumentRepo = function (faces) {
+ var font = this.userRepo.getFont(f);
+ var userFont = this.documentRepo.getFont(f);
+ if (userFont) {
+- if (!font.autoEmbed) {
++ if (font && !font.autoEmbed) {
+ this.documentRepo.removeFont(userFont);
+ }
+ return;
+diff --git a/app/pencil-core/common/canvas-external-renderer.js b/app/pencil-core/common/canvas-external-renderer.js
+index 2f5f6de..86439f1 100644
+--- a/app/pencil-core/common/canvas-external-renderer.js
++++ b/app/pencil-core/common/canvas-external-renderer.js
+@@ -94,7 +94,7 @@ function init() {
+ if (ext == ".png") mine = "image/png";
+
+ fs.readFile(sourcePath, function (error, bitmap) {
+- var url = "data:" + mime + ";base64," + new Buffer(bitmap).toString("base64");
++ var url = "data:" + mime + ";base64," + Buffer.from(bitmap).toString("base64");
+
+ image.setAttributeNS(xlink, "href", url);
+ totalImageLength += url.length;
+diff --git a/app/pencil-core/common/config.js b/app/pencil-core/common/config.js
+index 5e14de7..ca91c8c 100644
+--- a/app/pencil-core/common/config.js
++++ b/app/pencil-core/common/config.js
+@@ -73,3 +73,4 @@ Config.DEVICE_ADB_PATH = Config.define("device.adb_path", "adb");
+ Config.EXPORT_CROP_FOR_CLIPBOARD = Config.define("export.crop_for_clipboard", false);
+ Config.EXPORT_DEFAULT_SCALE = Config.define("export.default_scale", 1);
+ Config.EXPORT_DEFAULT_BACKGROUND_COLOR = Config.define("export.default_background_color", "");
++Config.CORE_USE_HWA = Config.define("core.useHardwareAcceleration", false);
+diff --git a/app/pencil-core/common/controller.js b/app/pencil-core/common/controller.js
+index 8a6e2c2..7f92fdb 100644
+--- a/app/pencil-core/common/controller.js
++++ b/app/pencil-core/common/controller.js
+@@ -1158,12 +1158,13 @@ Controller.prototype.rasterizeCurrentPage = function (targetPage) {
+ filters: [
+ { name: "PNG Image (*.png)", extensions: ["png"] }
+ ]
+- }, function (filePath) {
+- if (!filePath) return;
++ }).then(function (res) {
++ if (!res || !res.filePath) return;
++ var filePath = res.filePath;
+ this.applicationPane.rasterizer.rasterizePageToFile(page, filePath, function (p, error) {
+ if (!error) {
+ NotificationPopup.show("Page exprted as '" + path.basename(filePath) + "'.", "View", function () {
+- shell.openItem(filePath);
++ shell.openPath(filePath);
+ });
+ }
+ }, undefined, false, options);
+@@ -1204,7 +1205,8 @@ Controller.prototype.copyPageBitmap = function (targetPage) {
+ var filePath = tmp.tmpNameSync();
+ thiz.applicationPane.rasterizer.rasterizePageToFile(page, filePath, function (p, error) {
+ if (!error) {
+- clipboard.writeImage(filePath);
++ var image = nativeImage.createFromPath(filePath);
++ var result = clipboard.writeImage(image);
+ fs.unlinkSync(filePath);
+ NotificationPopup.show("Page bitmap copied into clipboard.");
+ }
+@@ -1232,7 +1234,8 @@ Controller.prototype.rasterizeSelection = function (options) {
+
+ this.applicationPane.rasterizer.rasterizeSelectionToFile(target, filePath, function (p, error) {
+ if (!error) {
+- clipboard.writeImage(filePath);
++ var image = nativeImage.createFromPath(filePath);
++ clipboard.writeImage(image);
+ fs.unlinkSync(filePath);
+ NotificationPopup.show("Page bitmap copied into clipboard.");
+ }
+@@ -1244,12 +1247,13 @@ Controller.prototype.rasterizeSelection = function (options) {
+ filters: [
+ { name: "PNG Image (*.png)", extensions: ["png"] }
+ ]
+- }, function (filePath) {
+- if (!filePath) return;
++ }).then(function (res) {
++ if (!res || !res.filePath) return;
++ var filePath = res.filePath;
+ this.applicationPane.rasterizer.rasterizeSelectionToFile(target, filePath, function (p, error) {
+ if (!error) {
+ NotificationPopup.show("Selection exprted as '" + path.basename(filePath) + "'.", "View", function () {
+- shell.openItem(filePath);
++ shell.openPath(filePath);
+ });
+ }
+ }, undefined, options);
+@@ -1684,14 +1688,14 @@ Controller.prototype.exportAsLayout = function () {
+ title: "Export Layout",
+ defaultPath: defaultPath,
+ filters: [{name: 'XHTML Layout', extensions: ["xhtml"]}]
+- }, function (filePath) {
+- if (filePath) {
+- outputPath = filePath;
+- outputImage = path.join(path.dirname(outputPath), IMAGE_FILE);
+- Pencil.rasterizer.rasterizePageToFile(thiz.activePage, outputImage, function (p, error) {
+- done();
+- });
+- }
++ }).then(function (res) {
++ if (!res || !res.filePath) return;
++ var filePath = res.filePath;
++ outputPath = filePath;
++ outputImage = path.join(path.dirname(outputPath), IMAGE_FILE);
++ Pencil.rasterizer.rasterizePageToFile(thiz.activePage, outputImage, function (p, error) {
++ done();
++ });
+ });
+ };
+
+diff --git a/app/pencil-core/common/externalEditorSupports.js b/app/pencil-core/common/externalEditorSupports.js
+index e55d600..4e187d5 100644
+--- a/app/pencil-core/common/externalEditorSupports.js
++++ b/app/pencil-core/common/externalEditorSupports.js
+@@ -6,6 +6,15 @@ ExternalEditorSupports.getEditorPath = function (extension) {
+ || extension == "gif"
+ || extension == "png") return Config.get("external.editor.bitmap.path", "/usr/bin/gimp -n %f");
+
++ var configName = "external.editor." + extension + ".path";
++ var p = Config.get(configName, null);
++
++ if (p) {
++ return p;
++ } else if (p == null) {
++ Config.define(configName, "");
++ }
++
+ throw Util.getMessage("unsupported.type", extension);
+ };
+ ExternalEditorSupports.queue = [];
+@@ -39,14 +48,22 @@ ExternalEditorSupports.handleEditRequest = function (contentProvider, contentRec
+ params.push(tmpFile.name);
+ }
+
++ var startedAt = new Date().getTime();
+ var process = spawn(executablePath, params);
+
+ var timeOutId = null;
+ process.on("close", function () {
++ var closedAt = new Date().getTime();
++ console.log("Closed: ", closedAt - startedAt);
++ if (closedAt - startedAt < 2000) {
++ Dialog.alert("Editor exited almost immediately", "The external edit has exited almost immediately after be launched. This is usually caused by the case where an existing instance of the editor software is running. Please make sure that is not the case.");
++ }
+ try {
+ contentReceiver.update(tmpFile.name);
+ if (timeOutId) window.clearTimeout(timeOutId);
+- tmpFile.removeCallback();
++ window.setTimeout(function () {
++ tmpFile.removeCallback();
++ }, 3000);
+ } catch (e) {
+ console.error(e);
+ }
+diff --git a/app/pencil-core/common/renderer.js b/app/pencil-core/common/renderer.js
+index 7cbc182..5e13b2e 100644
+--- a/app/pencil-core/common/renderer.js
++++ b/app/pencil-core/common/renderer.js
+@@ -78,7 +78,7 @@ module.exports = function () {
+ if (err) throw err;
+
+ var svg = data.svg;
+- var delay = 10;
++ var delay = 500;
+ console.log("data.scale", data.scale);
+ var scale = typeof(data.scale) == "number" ? data.scale : 1;
+
+@@ -112,8 +112,10 @@ module.exports = function () {
+ + ' window.setTimeout(function () {\n'
+ + ' window.requestAnimationFrame(function () {\n'
+ + ' window.requestAnimationFrame(function () {\n'
+- + ' ipcRenderer.send("render-rendered", {objectsWithLinking: window.objectsWithLinking});\n'
+- + ' console.log("Rendered signaled");\n'
++ + ' window.setTimeout(function () {\n'
++ + ' ipcRenderer.send("render-rendered", {objectsWithLinking: window.objectsWithLinking});\n'
++ + ' console.log("Rendered signaled");\n'
++ + ' }, 500);\n'
+ + ' });\n'
+ + ' });\n'
+ + ' }, ' + delay + ');\n'
+@@ -141,7 +143,7 @@ module.exports = function () {
+ event.sender.send(data.id, {url: "", objectsWithLinking: renderedData.objectsWithLinking});
+ __callback();
+ } else {
+- rendererWindow.capturePage(function (nativeImage) {
++ rendererWindow.capturePage().then(function (nativeImage) {
+ var dataURL = nativeImage.toDataURL();
+
+ cleanupCallback();
+diff --git a/app/pencil-core/common/shared-util.js b/app/pencil-core/common/shared-util.js
+index 3e8864a..3bae7f0 100644
+--- a/app/pencil-core/common/shared-util.js
++++ b/app/pencil-core/common/shared-util.js
+@@ -51,7 +51,7 @@ module.exports = function () {
+ var format = FORMAT_MAP[ext];
+ if (!format) format = "truetype";
+
+- var url = "data:" + mime + ";base64," + new Buffer(bytes).toString("base64");
++ var url = "data:" + mime + ";base64," + Buffer.from(bytes).toString("base64");
+
+ combinedCSS += "@font-face {\n"
+ + " font-family: '" + installedFace.name + "';\n"
+diff --git a/app/pencil-core/common/svgRasterizer.js b/app/pencil-core/common/svgRasterizer.js
+index 977c8c7..398d1f2 100644
+--- a/app/pencil-core/common/svgRasterizer.js
++++ b/app/pencil-core/common/svgRasterizer.js
+@@ -44,7 +44,7 @@ Rasterizer.ipcBasedBackend = {
+ // if (scale != 1) {
+ // svgNode.setAttribute("width", w + "px");
+ // svgNode.setAttribute("height", h + "px");
+- //
++ //
+ // svgNode.setAttribute("viewBox", "0 0 " + width + " " + height);
+ // }
+
+@@ -125,7 +125,7 @@ Rasterizer.inProcessCanvasBasedBackend = {
+ if (ext == ".png") mine = "image/png";
+
+ fs.readFile(sourcePath, function (error, bitmap) {
+- var url = "data:" + mime + ";base64," + new Buffer(bitmap).toString("base64");
++ var url = "data:" + mime + ";base64," + Buffer.from(bitmap).toString("base64");
+
+ image.setAttributeNS(PencilNamespaces.xlink, "href", url);
+ totalImageLength += url.length;
+@@ -167,9 +167,67 @@ Rasterizer.inProcessCanvasBasedBackend = {
+ convertNext();
+ }
+ };
++Rasterizer.inProcessFileCanvasBasedBackend = {
++ init: function () {
++ //the in-process rasterize requires basicly nothing to init :)
++ },
++ rasterize: function (svgNode, width, height, s, callback, parseLinks, options) {
++ console.log("rasterize() called", [svgNode, width, height, s, callback, parseLinks, options]);
++ var images = svgNode.querySelectorAll("image");
++ var totalImageLength = 0;
++
++ for (var i = 0; i < images.length; i ++) {
++ var image = images[i];
++ var href = image.getAttributeNS(PencilNamespaces.xlink, "href");
++ if (href && href.match("^file://(.+)$")) {
++ var sourcePath = decodeURI(RegExp.$1);
++ try {
++ fs.accessSync(sourcePath, fs.R_OK);
++ image.setAttribute("crossorigin", "anonymous");
++ } catch (e) {
++ image.setAttributeNS(PencilNamespaces.xlink, "href", "");
++ }
++ }
++ }
++
++ var tempFile = tmp.fileSync({postfix: ".svg" });
++ fs.writeFileSync(tempFile.name, Controller.serializer.serializeToString(svgNode), XMLDocumentPersister.CHARSET);
++
++ var delay = 500;
++
++ var canvas = document.createElement("canvas");
++ canvas.setAttribute("style", "display: none;");
++ canvas.setAttribute("width", width * s);
++ canvas.setAttribute("height", height * s);
++ document.body.appendChild(canvas);
++ var ctx = canvas.getContext("2d");
++
++ var img = document.createElement("img");
+
++ img.onload = function () {
++ ctx.save();
++ ctx.scale(s, s);
++ ctx.drawImage(img, 0, 0);
++ ctx.setTransform(1, 0, 0, 1, 0, 0);
++
++ setTimeout(function () {
++ callback(canvas.toDataURL());
++ ctx.restore();
++ img.onload = null;
++ img.src = "";
++ }, delay);
++ };
++
++ img.setAttribute("crossorigin", "anonymous");
++ img.setAttribute("style", "display: none;");
++ document.body.appendChild(img);
++
++ img.setAttribute("src", "file://" + tempFile.name);
++ }
++};
+ Rasterizer.prototype.getBackend = function () {
+ //TODO: options or condition?
++ // return Rasterizer.inProcessFileCanvasBasedBackend;
+ return Rasterizer.ipcBasedBackend;
+ // return Rasterizer.outProcessCanvasBasedBackend;
+ };
+@@ -194,7 +252,7 @@ Rasterizer.prototype.rasterizePageToUrl = function (page, callback, scale, parse
+ g.appendChild(svg.removeChild(svg.firstChild));
+ }
+ svg.appendChild(g);
+-
++
+ w -= 2 * m;
+ h -= 2 * m;
+ svg.setAttribute("width", w);
+@@ -281,7 +339,7 @@ Rasterizer.prototype.rasterizePageToFile = function (page, filePath, callback, s
+ var base64Data = dataURI;
+ if (base64Data.startsWith(prefix)) base64Data = base64Data.substring(prefix.length);
+
+- var buffer = new Buffer(base64Data, "base64");
++ var buffer = Buffer.from(base64Data, "base64");
+ fs.writeFile(actualPath, buffer, "utf8", function (err) {
+ console.log("Finish rasterizing page: ", page.name, actualPath);
+ callback(parseLinks ? {actualPath: actualPath, objectsWithLinking: data.objectsWithLinking} : actualPath, err);
+@@ -290,7 +348,7 @@ Rasterizer.prototype.rasterizePageToFile = function (page, filePath, callback, s
+ };
+ Rasterizer.getExportScale = function (inputScale) {
+ if (typeof(inputScale) == "number") return inputScale;
+-
++
+ var configScale = Config.get(Config.EXPORT_DEFAULT_SCALE, 1.0);
+ if (typeof(configScale) == "number") {
+ return configScale;
+@@ -347,7 +405,7 @@ Rasterizer.prototype.rasterizeSelectionToFile = function (target, filePath, call
+ var base64Data = dataURI;
+ if (base64Data.startsWith(prefix)) base64Data = base64Data.substring(prefix.length);
+
+- var buffer = new Buffer(base64Data, "base64");
++ var buffer = Buffer.from(base64Data, "base64");
+ fs.writeFile(actualPath, buffer, {}, function (err) {
+ callback(actualPath, err);
+ });
+diff --git a/app/pencil-core/common/util.js b/app/pencil-core/common/util.js
+index a6c761e..ac9ad79 100644
+--- a/app/pencil-core/common/util.js
++++ b/app/pencil-core/common/util.js
+@@ -2154,13 +2154,20 @@ Util.imageOnloadListener = function (event) {
+
+ };
+ Util.setupImage = function (image, src, mode, allowUpscale) {
+- image.onload = Util.imageOnloadListener;
+- image.style.visibility = "hidden";
+- image.style.width = "0px";
+- image.style.height = "0px";
+- image._mode = mode;
+- image._allowUpscale = allowUpscale;
++ // image.onload = Util.imageOnloadListener;
++ // image.style.visibility = "hidden";
++ image.style.width = "100%";
++ image.style.height = "100%";
++ image.style.opacity = "0";
+ image.src = src;
++
++ mode = mode || "center-crop";
++
++ var hp = (mode.indexOf("left") >= 0) ? "left" : ((mode.indexOf("right") >= 0) ? "right" : " center");
++ var vp = (mode.indexOf("top") >= 0) ? "top" : ((mode.indexOf("bottom") >= 0) ? "bottom" : " center");
++ image.parentNode.style.backgroundImage = "url('" + src + "')";
++ image.parentNode.style.backgroundPosition = hp + " " + vp;
++ image.parentNode.style.backgroundSize = (mode.indexOf("crop") >= 0) ? "cover" : "contain";
+ };
+
+ Util.isDev = function() {
+@@ -2466,6 +2473,33 @@ function copyFolderRecursiveSync(source, target) {
+ }
+ }
+
++function PropertyMask(names) {
++ this.names = (typeof(names) == "string") ? [names] : names;
++}
++PropertyMask.prototype.and = function (other) {
++ return new PropertyMask(this.names.concat.other.names);
++};
++PropertyMask.prototype.contains = function (name) {
++ return this.names.indexOf(name) >= 0;
++};
++PropertyMask.prototype.apply = function (original, newValue) {
++ if (!original || !newValue) return original;
++ var value = new original.constructor();
++ for (var name in original) {
++ if (original.hasOwnProperty(name)) {
++ value[name] = original[name];
++ }
++ }
++
++ for (var name of this.names) {
++ if (newValue.hasOwnProperty(name)) {
++ value[name] = newValue[name];
++ }
++ }
++
++ return value;
++};
++
+ function getStaticFilePath(subPath) {
+ var filePath = __dirname;
+ if (!subPath) return filePath;
+diff --git a/app/pencil-core/common/webPrinter.js b/app/pencil-core/common/webPrinter.js
+index f9db73a..27184d2 100644
+--- a/app/pencil-core/common/webPrinter.js
++++ b/app/pencil-core/common/webPrinter.js
+@@ -38,17 +38,7 @@ module.exports = function () {
+ // }
+ // }
+ // });
+- browserWindow.webContents.printToPDF(options, function(error, pdfBuffer) {
+- if (error) {
+- try {
+- global.mainWindow.webContents.send(data.id, {success: false, message: error.message});
+- } finally {
+- __callback();
+- }
+-
+- return;
+- }
+-
++ browserWindow.webContents.printToPDF(options).then(function(pdfBuffer) {
+ fs.writeFile(data.targetFilePath, pdfBuffer, function(error) {
+ try {
+ if (error) {
+@@ -60,6 +50,12 @@ module.exports = function () {
+ } finally {
+ __callback();
+ }
++ }).catch (function (error) {
++ try {
++ global.mainWindow.webContents.send(data.id, {success: false, message: error.message});
++ } finally {
++ __callback();
++ }
+ })
+ });
+ } else {
+diff --git a/app/pencil-core/definition/collectionManager.js b/app/pencil-core/definition/collectionManager.js
+index d3962ad..2435b5d 100644
+--- a/app/pencil-core/definition/collectionManager.js
++++ b/app/pencil-core/definition/collectionManager.js
+@@ -313,10 +313,10 @@ CollectionManager.installNewCollection = function (callback) {
+ { name: "Stencil files", extensions: ["zip", "epc"] }
+ ]
+
+- }, function (filenames) {
+- if (!filenames || filenames.length <= 0) return;
+- Config.set("collection.install.recentlyDirPath", path.dirname(filenames[0]));
+- CollectionManager.installCollectionFromFilePath(filenames[0], callback);
++ }).then(function (res) {
++ if (!res || !res.filePaths || res.filePaths.length <= 0) return;
++ Config.set("collection.install.recentlyDirPath", path.dirname(res.filePaths[0]));
++ CollectionManager.installCollectionFromFilePath(res.filePaths[0], callback);
+ });
+ };
+
+diff --git a/app/pencil-core/editor/geometryEditor.js b/app/pencil-core/editor/geometryEditor.js
+index 13ab8b2..824770a 100644
+--- a/app/pencil-core/editor/geometryEditor.js
++++ b/app/pencil-core/editor/geometryEditor.js
+@@ -16,6 +16,7 @@ GeometryEditor.prototype.resetAccomulatedChanges = function () {
+ };
+ GeometryEditor.prototype.install = function (canvas) {
+ this.canvas = canvas;
++ this.canvas.geometryEditor = this;
+ this.canvas.onScreenEditors.push(this);
+ this.svgElement = canvas.ownerDocument.importNode(Dom.getSingle("/p:Config/svg:g", GeometryEditor.configDoc), true);
+
+@@ -314,6 +315,7 @@ GeometryEditor.prototype.handleMouseUp = function (event) {
+ }, this, Util.getMessage("action.move.shape"));
+ }
+ } finally {
++ console.log("Setting currentAnchor = null");
+ this.currentAnchor = null;
+ }
+ };
+@@ -361,6 +363,7 @@ GeometryEditor.prototype.handleMouseMove = function (event) {
+ var uPoint1 = Svg.vectorInCTM(new Point(this.oX, this.oY), this.geo.ctm);
+ var uPoint2 = Svg.vectorInCTM(new Point(event.clientX, event.clientY), this.geo.ctm);
+
++
+ var matrix = this.currentAnchor._matrix;
+ var t = event.shiftKey ? {x: 1, y: 1} : this.getGridSize(); //Svg.vectorInCTM(this.getGridSize(), this.geo.ctm, true);
+ var grid = {w: t.x * this.canvas.zoom, h: t.y * this.canvas.zoom};
+@@ -390,6 +393,8 @@ GeometryEditor.prototype.handleMouseMove = function (event) {
+ var controller = this.canvas.currentController;
+ var bound = controller.getBounding();
+
++ var xsnap = null;
++
+ //HORIZONTAL
+ if (!locking.width) {
+ dx = matrix.dx * mdx;
+@@ -404,28 +409,13 @@ GeometryEditor.prototype.handleMouseMove = function (event) {
+
+ var delta = newXNormalized - newX;
+
+- if (!locking.ratio) {
++ if (!locking.ratio && !event.shiftKey) {
+ var snapping = this._lastGuides.left ? this._lastGuides.left.clone() :
+ new SnappingData("Left", bound.x, "Left", true, Util.newUUID());
+- snapping.pos += dx;
+- var snap = this.canvas.snappingHelper.findSnapping(true, false, {
+- vertical: [ snapping ], horizontal: []
+- }, (grid.w / 2) - 1);
+-
+- if (snap && (snap.dx != 0 && !this.canvas.snappingHelper.snappedX)) {
+- this.canvas.snappingHelper.snappedX = true;
+- this.canvas.snappingHelper.snapX = newX;
+- delta = snap.dx;
+- } else {
+- var unsnapX = (this.canvas.snappingHelper.snapX != 0 && (Math.abs(this.canvas.snappingHelper.snapX - newX) > grid.w / 2));
+- if (unsnapX || !this.canvas.snappingHelper.snappedX) {
+- this.canvas.snappingHelper.snapX = 0;
+- this.canvas.snappingHelper.snappedX = false;
+- this.canvas.snappingHelper.clearSnappingGuideX();
+- } else {
+- delta = snap.dx;
+- }
+- }
++
++ xsnap = this.canvas.snappingHelper.applySnappingValue(dx, [snapping], this.canvas.snappingHelper.lastXData, this.canvas.currentController);
++ if (xsnap) delta = xsnap.d - dx;
++ this.canvas.snappingHelper.drawSnaps(xsnap, null);
+ }
+
+ dx += delta;
+@@ -438,28 +428,13 @@ GeometryEditor.prototype.handleMouseMove = function (event) {
+
+ var delta = newX2Normalized - newX2;
+
+- if (!locking.ratio) {
++ if (!locking.ratio && !event.shiftKey) {
+ var snapping = this._lastGuides.right ? this._lastGuides.right.clone() :
+- new SnappingData("Right", bound.x + bound.width, "Right", true, Util.newUUID());
+- snapping.pos += dw;
+-
+- var snap = this.canvas.snappingHelper.findSnapping(true, false, {
+- vertical: [snapping], horizontal: []
+- }, (grid.w / 2) - 1);
+- if (snap && (snap.dx != 0 && !this.canvas.snappingHelper.snappedX)) {
+- this.canvas.snappingHelper.snappedX = true;
+- this.canvas.snappingHelper.snapX = newX2;
+- delta = snap.dx;
+- } else {
+- var unsnapX = (this.canvas.snappingHelper.snapX != 0 && (Math.abs(this.canvas.snappingHelper.snapX - newX2) > grid.w / 2));
+- if (unsnapX || !this.canvas.snappingHelper.snappedX) {
+- this.canvas.snappingHelper.snapX = 0;
+- this.canvas.snappingHelper.snappedX = false;
+- this.canvas.snappingHelper.clearSnappingGuideX();
+- } else {
+- delta = snap.dx;
+- }
+- }
++ new SnappingData("Right", bound.x + bound.width, "Right", true, Util.newUUID());
++
++ xsnap = this.canvas.snappingHelper.applySnappingValue(dw, [snapping], this.canvas.snappingHelper.lastXData, this.canvas.currentController);
++ if (xsnap) delta = xsnap.d - dw;
++ this.canvas.snappingHelper.drawSnaps(xsnap, null);
+ }
+
+ dw += delta;
+@@ -481,29 +456,13 @@ GeometryEditor.prototype.handleMouseMove = function (event) {
+
+ var delta = newYNormalized - newY;
+
+- if (!locking.ratio) {
++ if (!locking.ratio && !event.shiftKey) {
+ var snapping = this._lastGuides.top ? this._lastGuides.top.clone() :
+- new SnappingData("Top", bound.y, "Top", true, Util.newUUID());
+- snapping.pos += dy;
+-
+- var snap = this.canvas.snappingHelper.findSnapping(false, true, {
+- vertical: [],
+- horizontal: [snapping]
+- }, (grid.w / 2) - 1);
+- if (snap && (snap.dy != 0 && !this.canvas.snappingHelper.snappedY)) {
+- this.canvas.snappingHelper.snappedY = true;
+- this.canvas.snappingHelper.snapY = newY;
+- delta = snap.dy;
+- } else {
+- var unsnapY = (this.canvas.snappingHelper.snapY != 0 && (Math.abs(this.canvas.snappingHelper.snapY - newY) > grid.w / 2));
+- if (unsnapY || !this.canvas.snappingHelper.snappedY) {
+- this.canvas.snappingHelper.snapY = 0;
+- this.canvas.snappingHelper.snappedY = false;
+- this.canvas.snappingHelper.clearSnappingGuideY();
+- } else {
+- delta = snap.dy;
+- }
+- }
++ new SnappingData("Top", bound.y, "Top", true, Util.newUUID());
++
++ var ysnap = this.canvas.snappingHelper.applySnappingValue(dy, [snapping], this.canvas.snappingHelper.lastYData, this.canvas.currentController);
++ if (ysnap) delta = ysnap.d - dy;
++ this.canvas.snappingHelper.drawSnaps(xsnap, ysnap);
+ }
+
+ dy += delta;
+@@ -515,30 +474,13 @@ GeometryEditor.prototype.handleMouseMove = function (event) {
+
+ var delta = newY2Normalized - newY2;
+
+- if (!locking.ratio) {
++ if (!locking.ratio && !event.shiftKey) {
+ var snapping = this._lastGuides.bottom ? this._lastGuides.bottom.clone() :
+- new SnappingData("Bottom", bound.y + bound.height, "Bottom", true, Util.newUUID());
+- snapping.pos += dh;
+-
+-
+- var snap = this.canvas.snappingHelper.findSnapping(false, true, {
+- vertical: [],
+- horizontal: [snapping]
+- }, (grid.w / 2) - 1);
+- if (snap && (snap.dy != 0 && !this.canvas.snappingHelper.snappedY)) {
+- this.canvas.snappingHelper.snappedY = true;
+- this.canvas.snappingHelper.snapY = newY2;
+- delta = snap.dy;
+- } else {
+- var unsnapY = (this.canvas.snappingHelper.snapY != 0 && (Math.abs(this.canvas.snappingHelper.snapY - newY2) > grid.w / 2));
+- if (unsnapY || !this.canvas.snappingHelper.snappedY) {
+- this.canvas.snappingHelper.snapY = 0;
+- this.canvas.snappingHelper.snappedY = false;
+- this.canvas.snappingHelper.clearSnappingGuideY();
+- } else {
+- delta = snap.dy;
+- }
+- }
++ new SnappingData("Bottom", bound.y + bound.height, "Bottom", true, Util.newUUID());
++
++ var ysnap = this.canvas.snappingHelper.applySnappingValue(dh, [snapping], this.canvas.snappingHelper.lastYData, this.canvas.currentController);
++ if (ysnap) delta = ysnap.d - dh;
++ this.canvas.snappingHelper.drawSnaps(xsnap, ysnap);
+ }
+
+ dh += delta;
+diff --git a/app/pencil-core/exporter/documentExportManager.js b/app/pencil-core/exporter/documentExportManager.js
+index 872a0e5..ae6bf1c 100644
+--- a/app/pencil-core/exporter/documentExportManager.js
++++ b/app/pencil-core/exporter/documentExportManager.js
+@@ -36,6 +36,7 @@ DocumentExportManager.prototype.generateFriendlyId = function (page, usedFriendl
+ return name;
+ };
+ DocumentExportManager.prototype._exportDocumentWithParamsImpl = function (doc, forcedExporterId, params) {
++ console.log("Export requested...");
+ var exporter = Pencil.getDocumentExporterById(params.exporterId);
+ if (!exporter) return;
+
+@@ -140,13 +141,14 @@ DocumentExportManager.prototype._exportDocumentWithParamsImpl = function (doc, f
+ thiz._exportDocumentToXML(doc, pages, pageExtraInfos, destFile, params, function () {
+ listener.onTaskDone();
+ NotificationPopup.show(Util.getMessage("document.has.been.exported", destFile), "View", function () {
+- shell.openItem(destFile);
++ shell.openPath(destFile);
+ });
+ });
+ })
+ return;
+ }
+ var page = pages[pageIndex];
++ console.log("Rasiterizing " + page.name);
+
+ //signal progress
+ var task = Util.getMessage("exporting.page.no.prefix", page.name);
+@@ -184,7 +186,7 @@ DocumentExportManager.prototype._exportDocumentWithParamsImpl = function (doc, f
+ listener.onTaskDone();
+ if (destFile) {
+ NotificationPopup.show(Util.getMessage("document.has.been.exported", destFile), "View", function () {
+- shell.openItem(destFile);
++ shell.openPath(destFile);
+ });
+ } else {
+ NotificationPopup.show("Document has been exported.");
+@@ -407,7 +409,8 @@ DocumentExportManager.prototype._exportDocumentToXML = function (doc, pages, pag
+
+ try {
+ exporter.export(this.doc, exportSelection, destFile, xmlFile.name, function () {
+- xmlFile.removeCallback();
++ console.log("xmlFile:" + xmlFile.name);
++ //xmlFile.removeCallback();
+ callback();
+ });
+ } catch (e) {
+diff --git a/app/pencil-core/privateCollection/privateCollectionManager.js b/app/pencil-core/privateCollection/privateCollectionManager.js
+index b4aaa6b..fed0dbd 100644
+--- a/app/pencil-core/privateCollection/privateCollectionManager.js
++++ b/app/pencil-core/privateCollection/privateCollectionManager.js
+@@ -11,8 +11,7 @@ PrivateCollectionManager.loadPrivateCollections = function () {
+ var privateCollectionXmlLocation = path.join(PrivateCollectionManager.getPrivateCollectionDirectory(), "PrivateCollection.xml");
+
+ // privateCollectionXmlLocation.append("PrivateCollection.xml");
+- var stat = fs.statSync(privateCollectionXmlLocation);
+- if (!stat) return;
++ if (!fs.existsSync(privateCollectionXmlLocation)) return;
+
+ // var fileContents = FileIO.read(privateCollectionXmlLocation, ShapeDefCollectionParser.CHARSET);
+ var fileContents = fs.readFileSync(privateCollectionXmlLocation, ShapeDefCollectionParser.CHARSET);
+diff --git a/app/pencil-core/propertyType/alignment.js b/app/pencil-core/propertyType/alignment.js
+index 2adca1c..a7a49a0 100644
+--- a/app/pencil-core/propertyType/alignment.js
++++ b/app/pencil-core/propertyType/alignment.js
+@@ -2,6 +2,10 @@ function Alignment(h, v) {
+ this.h = h ? h : 0;
+ this.v = v ? v : 0;
+ }
++
++Alignment.H = new PropertyMask("h");
++Alignment.V = new PropertyMask("v");
++
+ Alignment.REG_EX = /^([0-9]+)\,([0-9]+)$/;
+ Alignment.fromString = function(literal) {
+ var align = new Alignment(0, 0);
+diff --git a/app/pencil-core/propertyType/font.js b/app/pencil-core/propertyType/font.js
+index 7b6d42e..971b0f4 100644
+--- a/app/pencil-core/propertyType/font.js
++++ b/app/pencil-core/propertyType/font.js
+@@ -9,6 +9,14 @@ function Font() {
+ Font.REG_EX = /^([^\|]+)\|([^\|]+)\|([^\|]+)\|([0-9]+[a-z]+)$/i;
+ Font.REG_EX_2 = /^([^\|]+)\|([^\|]+)\|([^\|]+)\|([0-9]+[a-z]+)\|([^\|]+)$/i;
+ Font.REG_EX_3 = /^([^\|]+)\|([^\|]+)\|([^\|]+)\|([0-9]+[a-z]+)\|([^\|]+)\|([0-9\.]+)$/i;
++
++Font.FAMILY = new PropertyMask("family");
++Font.STYLE = new PropertyMask("style");
++Font.WEIGHT = new PropertyMask("weight");
++Font.SIZE = new PropertyMask("size");
++Font.DECOR = new PropertyMask("decor");
++Font.LINE_HEIGHT = new PropertyMask("lineHeight");
++
+ Font.fromString = function (literal) {
+ var font = new Font();
+ font.decor = "none";
+diff --git a/app/pencil-core/propertyType/shadowStyle.js b/app/pencil-core/propertyType/shadowStyle.js
+index 26c4807..87c09f5 100644
+--- a/app/pencil-core/propertyType/shadowStyle.js
++++ b/app/pencil-core/propertyType/shadowStyle.js
+@@ -8,6 +8,12 @@ function ShadowStyle() {
+
+ ShadowStyle.DEFAULT_COLOR = "#000000";
+
++ShadowStyle.DX = new PropertyMask("dx");
++ShadowStyle.DY = new PropertyMask("dy");
++ShadowStyle.SIZE = new PropertyMask("size");
++ShadowStyle.OPACITY = new PropertyMask("opacity");
++ShadowStyle.COLOR = new PropertyMask("color");
++
+ ShadowStyle.REG_EX = /^([^\|]+)\|([^\|]+)\|([^\|]+)(\|([^\|]+))?(\|([^\|]+))?$/i;
+ ShadowStyle.fromString = function (literal) {
+ var shadowStyle = new ShadowStyle();
+diff --git a/app/pencil-core/propertyType/strokeStyle.js b/app/pencil-core/propertyType/strokeStyle.js
+index 350e92d..de574ac 100644
+--- a/app/pencil-core/propertyType/strokeStyle.js
++++ b/app/pencil-core/propertyType/strokeStyle.js
+@@ -2,6 +2,10 @@ function StrokeStyle(w, array) {
+ this.w = (typeof(w) == "number") ? w : 1;
+ this.array = typeof(array) != "undefined" ? array : null;
+ }
++
++StrokeStyle.W = new PropertyMask("w");
++StrokeStyle.ARRAY = new PropertyMask("array");
++
+ StrokeStyle.REG_EX = /^([0-9]+)\|([0-9 \,]*)$/;
+ StrokeStyle.fromString = function(literal) {
+ var style = new StrokeStyle();
+diff --git a/app/pencil-core/target/group.js b/app/pencil-core/target/group.js
+index 2a38d9d..1f411f6 100644
+--- a/app/pencil-core/target/group.js
++++ b/app/pencil-core/target/group.js
+@@ -75,9 +75,9 @@ Group.prototype.getProperties = function () {
+ Group.prototype.getPropertyGroups = function () {
+ return [this.propertyGroup];
+ };
+-Group.prototype.setProperty = function (name, value) {
++Group.prototype.setProperty = function (name, value, nested, mask) {
+ for (t in this.targets) {
+- this.targets[t].setProperty(name, value);
++ this.targets[t].setProperty(name, value, nested, mask);
+ }
+ };
+ Group.prototype.getProperty = function (name) {
+diff --git a/app/pencil-core/target/shape.js b/app/pencil-core/target/shape.js
+index 25807d7..fa7a2b2 100644
+--- a/app/pencil-core/target/shape.js
++++ b/app/pencil-core/target/shape.js
+@@ -362,7 +362,13 @@ Shape.prototype.evalExpression = function (expression, value) {
+ return defaultValue;
+ }
+ };
+-Shape.prototype.setProperty = function (name, value, nested) {
++Shape.prototype.setProperty = function (name, value, nested, mask) {
++ if (mask) {
++ try {
++ value = mask.apply(this.getProperty(name), value);
++ } catch (e) {
++ }
++ }
+ if (!nested) {
+ this._appliedTargets = [];
+ this.canvas.run( function () {
+@@ -1131,6 +1137,10 @@ Shape.prototype.getSnappingGuide = function () {
+
+ var customSnappingData = this.performAction("getSnappingGuide");
+ if (customSnappingData) {
++ if (customSnappingData._ignoreBuiltIns) {
++ vertical.length = 0;
++ horizontal.length = 0;
++ }
+ for (var i = 0; i < customSnappingData.length; i++) {
+ var data = customSnappingData[i];
+ var m = this.svg.getTransformToElement(this.canvas.drawingLayer);
+@@ -1146,7 +1156,7 @@ Shape.prototype.getSnappingGuide = function () {
+ ik = k;
+ }
+ }
+- if (ik != -1) {
++ if (ik != -1 && !customSnappingData._allowTypeDuplications) {
+ vertical[ik] = data;
+ } else {
+ vertical.push(data);
+@@ -1161,7 +1171,7 @@ Shape.prototype.getSnappingGuide = function () {
+ ik = k;
+ }
+ }
+- if (ik != -1) {
++ if (ik != -1 && !customSnappingData._allowTypeDuplications) {
+ horizontal[ik] = data;
+ } else {
+ horizontal.push(data);
+diff --git a/app/pencil-core/target/targetSet.js b/app/pencil-core/target/targetSet.js
+index 5194cc9..b3eaa3c 100644
+--- a/app/pencil-core/target/targetSet.js
++++ b/app/pencil-core/target/targetSet.js
+@@ -64,9 +64,9 @@ TargetSet.prototype.applyBehaviorForProperty = function (name) {
+ TargetSet.prototype.getPropertyGroups = function () {
+ return [this.propertyGroup];
+ };
+-TargetSet.prototype.setProperty = function (name, value) {
++TargetSet.prototype.setProperty = function (name, value, nested, mask) {
+ for (t in this.targets) {
+- this.targets[t].setProperty(name, value);
++ this.targets[t].setProperty(name, value, nested, mask);
+ }
+ };
+ TargetSet.prototype.getProperty = function (name, any) {
+diff --git a/app/pencil-core/templates/HTML/prototype.HTML/Resources/expand.png b/app/pencil-core/templates/HTML/prototype.HTML/Resources/expand.png
+new file mode 100644
+index 0000000..a906329
+Binary files /dev/null and b/app/pencil-core/templates/HTML/prototype.HTML/Resources/expand.png differ
+diff --git a/app/pencil-core/templates/HTML/prototype.HTML/Resources/fit.png b/app/pencil-core/templates/HTML/prototype.HTML/Resources/fit.png
+new file mode 100644
+index 0000000..497b3fc
+Binary files /dev/null and b/app/pencil-core/templates/HTML/prototype.HTML/Resources/fit.png differ
+diff --git a/app/pencil-core/templates/HTML/prototype.HTML/Resources/script.js b/app/pencil-core/templates/HTML/prototype.HTML/Resources/script.js
+index 1c1fed0..43e7767 100644
+--- a/app/pencil-core/templates/HTML/prototype.HTML/Resources/script.js
++++ b/app/pencil-core/templates/HTML/prototype.HTML/Resources/script.js
+@@ -8,77 +8,87 @@ function scaleMap(page, r) {
+ } else {
+ container.innerHTML = "";
+ }
+-
++
+ map.querySelectorAll("area").forEach(function (area) {
+ var original = area._originalCoords || area.getAttribute("coords");
+ area._originalCoords = original;
+ var coords = original.split(/\,/).map(function (v) {
+ return Math.round(parseFloat(v) / r);
+ });
+-
++
+ area.setAttribute("coords", coords.join(","));
+ var a = document.createElement("a");
+- a.style.left = coords[0] + "px";
+- a.style.top = coords[1] + "px";
+- a.style.width = (coords[2] - coords[0]) + "px";
+- a.style.height = (coords[3] - coords[1]) + "px";
++ a.style.left = convertRatio(coords[0]) + "px";
++ a.style.top = convertRatio(coords[1]) + "px";
++ a.style.width = convertRatio(coords[2] - coords[0]) + "px";
++ a.style.height = convertRatio(coords[3] - coords[1]) + "px";
+ a.setAttribute("href", area.getAttribute("href"));
+-
++
+ container.appendChild(a);
+ });
+ }
++function convertRatio(value) {
++ return parseFloat(value); // * window.devicePixelRatio;
++}
+
+ function fitImages() {
+ var pages = document.querySelectorAll("body > div.Page");
+ var W = 0;
+ var H = 0;
+-
++
+ var activePage = null;
+-
++
+ pages.forEach(function (page) {
+ if (page.offsetWidth == 0 || page.offsetHeight == 0) return;
+-
+- W = page.offsetWidth - 30;
+- H = page.offsetHeight - 30;
++
++ W = page.offsetWidth - (window.useExpandedMode ? 200 : 40);
++ H = page.offsetHeight - 40;
+ activePage = page;
+ });
+-
++
+ if (activePage && window.lastActivePage != activePage) {
+- document.querySelectorAll(".TOC > div").forEach(function (item) {
++ window.useExpandedMode = false;
++ invalidateExpandMode("dontFit");
++ window.lastSize = null;
++
++ document.body.querySelectorAll(".TOC > div").forEach(function (item) {
+ var matched = item.classList.contains("Page_" + activePage.id);
+ if (matched) {
++ document.title = item._name + " - " + window.originalTitle;
+ item.classList.add("Focused");
+ item.focus();
+ } else {
+ item.classList.remove("Focused");
+ }
+ });
++
++ window.lastActivePage = activePage;
+ }
+-
+-
++
++
+ if (W && H) {
+ if (window.lastSize && window.lastSize.W == W && window.lastSize.H == H) return;
+
+ var imgs = document.querySelectorAll("body > div.Page img");
+ imgs.forEach(function (img) {
+- var r = Math.max(img.naturalWidth / W, img.naturalHeight / H);
+-
+- if (r < 1) r = 1;
++ var r = window.useExpandedMode ? Math.min(img.naturalWidth / W, img.naturalHeight / H) : Math.max(img.naturalWidth / W, img.naturalHeight / H);
++
++ if (r < 1 && !window.useExpandedMode) r = 1;
+ var w = Math.round(img.naturalWidth / r);
+ var h = Math.round(img.naturalHeight / r);
+-
++
+ img.style.width = w + "px";
+ img.style.height = h + "px";
+-
++
+ img.setAttribute("width", w);
+ img.setAttribute("height", h);
+-
++
+ var page = img;
+ while (!page.classList.contains("Page")) page = page.parentNode;
+-
+- scaleMap(page, r);
++
++ scaleMap(page, r * (img._originalWidth / img.naturalWidth));
+ });
+-
++
+ window.lastSize = {W: W, H: H};
+ }
+ }
+@@ -90,7 +100,7 @@ function checkActivePage() {
+ if (!firstPage) firstPage = page;
+ if (page.offsetWidth != 0 && page.offsetHeight != 0) found = true;
+ });
+-
++
+ if (!found && firstPage) {
+ location.hash = "#" + firstPage.id;
+ fitImages();
+@@ -122,7 +132,7 @@ function handleMouseMove() {
+ if (!document.body.classList.contains("Active")) {
+ document.body.classList.add("Active");
+ }
+-
++
+ if (idleTimeout) window.clearTimeout(idleTimeout);
+ idleTimeout = window.setTimeout(function () {
+ document.body.classList.remove("Active");
+@@ -139,17 +149,17 @@ function buildThumbnail(url, callback) {
+ image.onload = function () {
+ var canvas = document.createElement('canvas');
+ var ctx = canvas.getContext('2d');
+-
++
+ var r = Math.max(image.width / THUMB_WIDTH, image.height / THUMB_HEIGHT);
+ var w = image.width / r, h = image.height / r;
+ canvas.width = w;
+ canvas.height = h;
+-
++
+ ctx.drawImage(image, 0, 0, w, h);
+-
++
+ callback(canvas.toDataURL('image/png'), w, h);
+ };
+-
++
+ image.src = url;
+ }
+
+@@ -160,65 +170,78 @@ function generateTOC() {
+ pages.forEach(function (page) {
+ var title = page.querySelector("h2");
+ var img = page.querySelector(".ImageContainer img");
+-
++
+ var item = document.createElement("div");
+ var imageWrapper = document.createElement("a");
+ var itemImage = document.createElement("img");
+-
++
+ item.classList.add("Page_" + page.id);
+ item.setAttribute("tabindex", 0);
+-
+- imageWrapper.style.width = THUMB_DISPLAY_SIZE + "px";
+-
++ item._name = title.textContent;
++
+ imageWrapper.setAttribute("href", "#" + page.id);
+
+ item.appendChild(imageWrapper);
+ var name = document.createElement("strong");
+ name.innerHTML = title.innerHTML;
+- item.appendChild(name);
+-
++
+ toc.appendChild(item);
+-
++
+ buildThumbnail(img.src, function (dataUrl, w, h) {
+ var r = Math.max(w / THUMB_DISPLAY_SIZE, h / THUMB_DISPLAY_SIZE);
+ var w = w / r, h = h / r;
+-
++
+ imageWrapper.appendChild(itemImage);
+ itemImage.style.width = w + "px";
+ itemImage.style.height = h + "px";
+ itemImage.src = dataUrl;
++
++ imageWrapper.appendChild(name);
+ });
+ });
+-
++
+ document.body.appendChild(toc);
+ }
+
++function invalidateExpandMode(dontFit) {
++ if (window.useExpandedMode) {
++ document.body.classList.add("ExpandMode");
++ } else {
++ document.body.classList.remove("ExpandMode");
++ }
++
++ if (!dontFit) fitImages();
++}
++
+ function boot() {
++ window.originalTitle = document.title;
+ document.addEventListener("mousemove", handleMouseMove);
+ var style = document.createElement("link");
+ style.setAttribute("rel", "stylesheet");
+ style.setAttribute("href", "Resources/style.css");
+ document.querySelector("head").appendChild(style);
+- workingThreadFunction();
+- generateTOC();
+-}
+-
+-window.onload = boot;
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+
++ var imgs = document.querySelectorAll("body > div.Page img");
++ imgs.forEach(function (img) {
++ img._originalWidth = parseInt(img.getAttribute("width"), 10);
++ img._originalHeight = parseInt(img.getAttribute("height"), 10);
++ });
+
+
++ generateTOC();
++ workingThreadFunction();
+
+
++ window.zoomToggleButton = document.createElement("button");
++ window.zoomToggleButton.classList.add("ToggleZoomButton");
++ window.zoomToggleButton.setAttribute("title", "Toggle expand/fit mode");
++ document.body.appendChild(window.zoomToggleButton);
+
++ window.zoomToggleButton.addEventListener("click", function () {
++ window.useExpandedMode = window.useExpandedMode ? false : true;
++ window.lastSize = null;
++ invalidateExpandMode();
++ }, false);
++}
+
++window.onload = boot;
+diff --git a/app/pencil-core/templates/HTML/prototype.HTML/Resources/style.css b/app/pencil-core/templates/HTML/prototype.HTML/Resources/style.css
+index 1419b77..1c967f8 100644
+--- a/app/pencil-core/templates/HTML/prototype.HTML/Resources/style.css
++++ b/app/pencil-core/templates/HTML/prototype.HTML/Resources/style.css
+@@ -20,14 +20,22 @@ body, html {
+ top: 0px;
+ right: 0px;
+ bottom: 0px;
+-
++ background: #00000077;
++ text-align: center;
++}
++body:not(.ExpandMode) .Page {
+ display: flex;
++ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+- background: #00000077;
++}
++body.ExpandMode .Page {
++ overflow: auto;
++ padding: 20px;
++ text-align: center;
+ }
+ .Page:not(:target) {
+- display: none;
++ display: none !important;
+ }
+
+ .Page > h2 {
+@@ -36,7 +44,10 @@ body, html {
+ .Page .ImageContainer {
+ box-shadow: 0px 0px 1em #00000077;
+ }
+-
++body.ExpandMode .Page .ImageContainer {
++ display: inline-block;
++ margin-bottom: 20px;
++}
+ .Page .ImageContainer {
+ position: relative;
+ }
+@@ -60,7 +71,7 @@ body.Active .Page .ImageContainer > .Links > a {
+ width: 230px;
+ display: flex;
+ flex-direction: column;
+-
++
+ overflow-y: auto;
+ overflow-x: visible;
+ z-index: 2;
+@@ -69,28 +80,36 @@ body.Active .Page .ImageContainer > .Links > a {
+ .TOC > div {
+ display: flex;
+ flex-direction: column;
+- align-items: center;
+- padding: 1em;
+- background: transparent;
+- transition: background 0.2s ease;
+ }
+-.TOC > div.Focused {
++.TOC > div.Focused > a {
+ background: #5294E2;
+ }
+-.TOC > div:not(.Focused):hover {
++.TOC > div:not(.Focused):hover > a {
+ background: #5294E266;
+ }
++.TOC > div:not(.Focused):not(:hover) {
++ opacity: 0.7;
++}
+ .TOC > div + div {
+ margin-top: 1em;
+ }
+
+ .TOC > div > a {
++ flex: 1 1 auto;
++ display: flex;
++ flex-direction: column;
++ align-items: center;
++ padding: 1em;
++ background: transparent;
++ transition: background 0.2s ease;
++ text-decoration: none;
++}
++.TOC > div > a > img {
+ display: block;
+ border: solid 2px #FFF;
+ box-shadow: 0px 0px 5px #000;
+ }
+-
+-.TOC > div > strong {
++.TOC > div > a > strong {
+ display: block;
+ text-align: center;
+ color: #FFF;
+@@ -98,32 +117,27 @@ body.Active .Page .ImageContainer > .Links > a {
+ margin-top: 0.3em;
+ }
+
++.ToggleZoomButton {
++ position: fixed;
++ top: 0.5em;
++ right: 0.5em;
++ width: 1.5em;
++ height: 1.5em;
++ padding: 0.3em;
++ box-sizing: content-box;
++ background: url(expand.png) 50% 50% no-repeat #CCCCCC;
++ background-origin: content-box;
++ background-size: contain;
+
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
++ border: solid 1px #777;
++}
++.ToggleZoomButton:active {
++ margin-top: 1px;
++ margin-left: 1px;
++}
++.ToggleZoomButton:not(:hover) {
++ opacity: 0.3;
++}
++body.ExpandMode .ToggleZoomButton {
++ background-image: url(fit.png)
++}
+diff --git a/app/pencil-core/xferHelper/pngImageXferHelper.js b/app/pencil-core/xferHelper/pngImageXferHelper.js
+index 5b04db6..6879391 100644
+--- a/app/pencil-core/xferHelper/pngImageXferHelper.js
++++ b/app/pencil-core/xferHelper/pngImageXferHelper.js
+@@ -32,7 +32,8 @@ PNGImageXferHelper.prototype.handleDataImpl = function (imageData, shapeId) {
+ this.canvas.insertShape(bitmapDef, this.canvas.lastMouse || {x: 10, y: 10});
+
+ if (this.canvas.currentController) {
+- var dim = new Dimension(imageData.w, imageData.h);
++ var ratio = window.devicePixelRatio || 1;
++ var dim = new Dimension(Math.round(imageData.w / ratio), Math.round(imageData.h / ratio));
+ this.canvas.currentController.setProperty("imageData", imageData);
+ this.canvas.currentController.setProperty("box", dim);
+ this.canvas.invalidateEditors();
+@@ -67,7 +68,8 @@ JPGGIFImageXferHelper.prototype.handleData = function (url) {
+ var thiz = this;
+
+ var handler = function (imageData) {
+- var dim = new Dimension(imageData.w, imageData.h);
++ var ratio = window.devicePixelRatio || 1;
++ var dim = new Dimension(Math.round(imageData.w / ratio), Math.round(imageData.h / ratio));
+ thiz.canvas.currentController.setProperty("imageData", imageData);
+ thiz.canvas.currentController.setProperty("box", dim);
+ thiz.canvas.invalidateEditors();
+diff --git a/app/views/ApplicationPane.js b/app/views/ApplicationPane.js
+index 704fbac..7e2f960 100644
+--- a/app/views/ApplicationPane.js
++++ b/app/views/ApplicationPane.js
+@@ -125,13 +125,24 @@ ApplicationPane.prototype.onAttached = function () {
+ // });
+ // });
+ // }, 100);
+-
+-
++
++
+ };
++
++Config.UI_CUSTOM_FONT_FAMILY = Config.define("ui.customUIFontFamily", "");
++Config.UI_CUSTOM_FONT_SIZE = Config.define("ui.customUIFontSize", "");
++
+ ApplicationPane.prototype.invalidateUIForConfig = function () {
+ debug("BOOT: invalidating UI using configuration");
+ var useCompactLayout = Config.get("view.useCompactLayout", false);
+ document.body.setAttribute("compact-layout", useCompactLayout);
++
++ var family = Config.get(Config.UI_CUSTOM_FONT_FAMILY);
++ if (family) document.body.style.fontFamily = family;
++
++ var size = Config.get(Config.UI_CUSTOM_FONT_SIZE);
++ if (size) document.body.style.fontSize = size;
++
+ this.toolBarSrollView.invalidate();
+ };
+ ApplicationPane.prototype.invalidateUIForControllerStatus = function () {
+@@ -172,19 +183,19 @@ ApplicationPane.prototype.createCanvas = function () {
+ var stencilToolbar = new StencilShapeCanvasToolbar().into(wrapper);
+
+ var canvas = null;
+-
+-
++
++
+ scrollPane.addEventListener("mousedown", function (e) {
+ scrollPane._mouseDownAt = e.timeStamp;
+ });
+-
++
+ scrollPane.addEventListener("mouseup", function (e) {
+ if (!scrollPane._mouseDownAt || (e.timeStamp - scrollPane._mouseDownAt) > 150) return;
+ if (!Dom.findParentWithClass(e.target, "CanvasWrapper")) {
+ if (!canvas.isSelectingRange) canvas.selectNone();
+ }
+ });
+-
++
+ canvas = new Canvas(container, null, scrollPane);
+
+ this.getCanvasContainer().appendChild(scrollPane);
+@@ -288,7 +299,7 @@ ApplicationPane.prototype.showStartupPane = function () {
+ const NO_CONTENT_VALUE = 22;
+ ApplicationPane.prototype.getNoContentValue = function () {
+ var compact = Config.get("view.useCompactLayout", false);
+- return compact ? 0 : NO_CONTENT_VALUE;
++ return compact ? 10 : NO_CONTENT_VALUE;
+ }
+ ApplicationPane.prototype.getCanvasToolbarHeight = function () {
+ return StencilCollectionBuilder.isDocumentConfiguredAsStencilCollection() ? Math.round(3 * Util.em()) : 0;
+diff --git a/app/views/ExportDialog.js b/app/views/ExportDialog.js
+index 9e9dcb4..34b6679 100644
+--- a/app/views/ExportDialog.js
++++ b/app/views/ExportDialog.js
+@@ -243,10 +243,10 @@ ExportDialog.prototype.getDialogActions = function () {
+ }
+
+ dialogOptions.filters = filters;
+- console.log("dialogOptions", dialogOptions);
+- dialog.showSaveDialog(dialogOptions, function (filename) {
+- if (!filename) return;
+- result.targetPath = filename;
++ dialog.showSaveDialog(dialogOptions).then(function (res) {
++ if (!res || !res.filePath) return;
++ result.targetPath = res.filePath;
++ console.log("Selected", res.filePath);
+
+ this.close(result);
+
+@@ -262,9 +262,9 @@ ExportDialog.prototype.getDialogActions = function () {
+ }
+ }
+
+- dialog.showOpenDialog(dialogOptions, function (filenames) {
+- if (!filenames || filenames.length <= 0) return;
+- result.targetPath = filenames[0];
++ dialog.showOpenDialog(dialogOptions).then(function (res) {
++ if (!res || !res.filePaths || res.filePaths.length <= 0) return;
++ result.targetPath = res.filePaths[0];
+
+ this.close(result);
+
+diff --git a/app/views/StartUpDocumentView.js b/app/views/StartUpDocumentView.js
+index 8e02e23..93bf998 100644
+--- a/app/views/StartUpDocumentView.js
++++ b/app/views/StartUpDocumentView.js
+@@ -24,7 +24,7 @@ function StartUpDocumentView() {
+ if (pinDocs && pinDocs.indexOf(filePath) >= 0) Dom.addClass(binding.pin, "Unpin");
+ if (thumbPath) {
+ window.setTimeout(function () {
+- Util.setupImage(binding.thumbnailImage, ImageData.filePathToURL(thumbPath), "center-crop", "allowUpscale");
++ Util.setupImage(binding.thumbnailImage, ImageData.filePathToURL(thumbPath), "center-top-crop", "allowUpscale");
+ }, 10);
+ }
+ binding._node._filePath = filePath;
+diff --git a/app/views/StartUpDocumentView.xhtml b/app/views/StartUpDocumentView.xhtml
+index 05e6eeb..3f136f9 100644
+--- a/app/views/StartUpDocumentView.xhtml
++++ b/app/views/StartUpDocumentView.xhtml
+@@ -44,7 +44,7 @@
+ }
+ @recentDocumentPane .Repeater-Document vbox[role="item"] .ImageContainer {
+ width: 22ex;
+- height: 25ex;
++ height: 14ex;
+ background: #FFF;
+ }
+ @recentDocumentPane .Repeater-Document vbox[role="item"] .InfoPane {
+diff --git a/app/views/collections/BaseCollectionPane.js b/app/views/collections/BaseCollectionPane.js
+index 9019fb0..aff7054 100644
+--- a/app/views/collections/BaseCollectionPane.js
++++ b/app/views/collections/BaseCollectionPane.js
+@@ -224,12 +224,13 @@ BaseCollectionPane.prototype.reload = function (selectedCollectionId) {
+ BaseCollectionPane.prototype.filterCollections = function () {
+ var filter = this.searchInput.value;
+ this.clearTextButton.style.display = filter != null && filter.length > 0 ? "block" : "none"
+- var collectionNodes = Dom.getList(".//*[@class='Item']", this.selectorPane);
++ var collectionNodes = this.selectorPane.querySelectorAll(".Item");
+ var hasLast = false;
+ var firstNode = null;
+ for (var i in collectionNodes) {
+ var collectionNode = collectionNodes[i];
+ var collection = collectionNodes[i]._collection;
++ if (!collection) continue;
+ collection._shapeCount = 0;
+ collection._filteredShapes = [];
+ if (!filter) {
+@@ -244,6 +245,7 @@ BaseCollectionPane.prototype.filterCollections = function () {
+ collection._filteredShapes.push(def);
+ }
+ }
++
+ if (collection._shapeCount <= 0) {
+ collectionNode.setAttribute("_hidden", true);
+ collectionNode.style.display = "none";
+diff --git a/app/views/collections/CollectionBrowserDialog.js b/app/views/collections/CollectionBrowserDialog.js
+index b2b7edf..e6917f8 100644
+--- a/app/views/collections/CollectionBrowserDialog.js
++++ b/app/views/collections/CollectionBrowserDialog.js
+@@ -9,9 +9,9 @@ function CollectionBrowserDialog (collectionPanel, managerDialog) {
+ this.managerDialog = managerDialog;
+ this.title = "Collection Repository";
+ this.subTitle = "Browse the user-contributed collection repository."
+-
++
+ var thiz = this;
+-
++
+ this.bind("e:TabChange", function (event) {
+ var tab = this.tabPane.getActiveTabPane();
+ if (!tab._initialized) {
+diff --git a/app/views/common/Dialog.xhtml b/app/views/common/Dialog.xhtml
+index fd43ad3..9acee3c 100644
+--- a/app/views/common/Dialog.xhtml
++++ b/app/views/common/Dialog.xhtml
+@@ -52,8 +52,8 @@
+ background: rgba(0, 0, 0, 0.05);
+ }
+ body @dialogFrame @dialogTitle {
+- color: #555555;
+- font-size: 1em;
++ color: #567ab4;
++ font-size: 1.3em;
+ display: block;
+ }
+ body @dialogFrame @dialogSubTitle {
+@@ -63,18 +63,18 @@
+ display: none;
+ }
+ body @dialogFrame @dialogTitleContainer {
+- padding: 0.5em;
+ }
+ body @dialogFrame @dialogHeaderPane {
+ margin-bottom: 0em;
+- background: #d3d3d3;
+ }
+ body @dialogFrame @dialogBody {
+ padding: 1.2em;
+ overflow: hidden;
+ }
+ body @dialogFrame @dialogHeaderPane {
+- padding: 0.4em 0.7em;
++ padding: 1.2em;
++ padding-bottom: 0.2em;
++ align-items: center;
+ }
+ body @dialogFrame @dialogBody {
+ padding-bottom: 0em;
+@@ -96,6 +96,8 @@
+ min-width: 6em;
+ text-align: center;
+ display: inline-block;
++ font-weight: bold;
++ text-transform: capitalize;
+ }
+
+ body @dialogFooter > hbox > button[mode='accept'] {
+diff --git a/app/views/common/PageThumbnailView.js b/app/views/common/PageThumbnailView.js
+index 46cadd9..fd1f0d2 100644
+--- a/app/views/common/PageThumbnailView.js
++++ b/app/views/common/PageThumbnailView.js
+@@ -1,24 +1,24 @@
+ function PageThumbnailView() {
+ BaseTemplatedWidget.call(this);
+- this.bind("load", function (event) {
+- var W = this.pageThumbnailContainer.offsetWidth - 2;
+- var H = this.pageThumbnailContainer.offsetHeight - 2;
+- var w = this.pageThumbnail.naturalWidth;
+- var h = this.pageThumbnail.naturalHeight;
+-
+- var r = Math.min(w/W, h/H);
+-
+- w /= r;
+- h /= r;
+-
+- this.pageThumbnail.style.width = w + "px";
+- this.pageThumbnail.style.height = h + "px";
+-
+- this.pageThumbnail.style.left = (W - w) / 2 + "px";
+- this.pageThumbnail.style.top = (H - h) / 2 + "px";
+-
+- this.pageThumbnail.style.visibility = "visible";
+- }, this.pageThumbnail);
++ // this.bind("load", function (event) {
++ // var W = this.pageThumbnailContainer.offsetWidth - 2;
++ // var H = this.pageThumbnailContainer.offsetHeight - 2;
++ // var w = this.pageThumbnail.naturalWidth;
++ // var h = this.pageThumbnail.naturalHeight;
++ //
++ // var r = Math.min(w/W, h/H);
++ //
++ // w /= r;
++ // h /= r;
++ //
++ // this.pageThumbnail.style.width = w + "px";
++ // this.pageThumbnail.style.height = h + "px";
++ //
++ // this.pageThumbnail.style.left = (W - w) / 2 + "px";
++ // this.pageThumbnail.style.top = (H - h) / 2 + "px";
++ //
++ // this.pageThumbnail.style.visibility = "visible";
++ // }, this.pageThumbnail);
+
+ this.pageThumbnail.style.visibility = "hidden";
+
+@@ -47,7 +47,7 @@ PageThumbnailView.prototype.setPage = function (page, childMenu) {
+ PageThumbnailView.prototype._updateUI = function () {
+ this.pageThumbnail.style.visibility = "hidden";
+ if (!this.page.children || this.page.children.length == 0) this.pageActionButton.style.visibility = "hidden";
+- if (this.page.thumbPath) this.pageThumbnail.src = this.page.thumbPath + "?time=" + (new Date().getTime());
++ if (this.page.thumbPath) Util.setupImage(this.pageThumbnail, this.page.thumbPath + "?time=" + (new Date().getTime()), "center-top-crop", "allowUpscale");
+ // this.pageTitle.appendChild(document.createTextNode(this.page.name));
+ this.pageTitle.innerHTML = Dom.htmlEncode(this.page.name);
+ this.node().setAttribute("title", this.page.name);
+diff --git a/app/views/common/ProgressiveJobDialog.xhtml b/app/views/common/ProgressiveJobDialog.xhtml
+index ba92c73..c233dc2 100644
+--- a/app/views/common/ProgressiveJobDialog.xhtml
++++ b/app/views/common/ProgressiveJobDialog.xhtml
+@@ -7,7 +7,7 @@
+ }
+ @progressBar,
+ @statusLabel {
+- width: 18em;
++ width: 24em;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+@@ -15,6 +15,9 @@
+ height: 100%;
+ background: @selected_bg;
+ }
++ @statusLabel {
++ white-space: nowrap;
++ }
+ @container {
+ padding: 1em;
+ }
+diff --git a/app/views/common/Tree.js b/app/views/common/Tree.js
+index 2b7ae54..7ac27fa 100644
+--- a/app/views/common/Tree.js
++++ b/app/views/common/Tree.js
+@@ -96,7 +96,7 @@ var Tree = function() {
+ if ((target.checked && tree.options.propagateCheckActionDownwards)
+ || (!target.checked && tree.options.propagateUncheckActionDownwards)) {
+ var itemNode = target.parentNode.parentNode;
+- setItemsCheckedRecursivelyFromNodes(getChildrenContainerFromItemNode(itemNode).childNodes, target.checked);
++ if (!event.shiftKey) setItemsCheckedRecursivelyFromNodes(getChildrenContainerFromItemNode(itemNode).childNodes, target.checked);
+ }
+
+ Dom.emitEvent("blur", treeContainer, {});
+diff --git a/app/views/editors/AlignEditor.js b/app/views/editors/AlignEditor.js
+index 88132c2..84d2dac 100644
+--- a/app/views/editors/AlignEditor.js
++++ b/app/views/editors/AlignEditor.js
+@@ -10,12 +10,12 @@ AlignEditor.prototype.setup = function () {
+ AlignEditor.prototype._handleClick = function (event) {
+ var button = Dom.findParentByTagName(event.target, "button");
+ if (!button) return;
+-
++
+ button.parentNode.querySelectorAll("button").forEach(function (b) {
+ b.setAttribute("checked", b == button);
+ });
+-
+- this.fireChangeEvent();
++
++ this.fireChangeEvent(button.parentNode == this.horizontalGroup ? Alignment.H : Alignment.V);
+ };
+ AlignEditor.prototype.setValue = function (alignment) {
+ this.horizontalGroup.querySelectorAll("button").forEach(function (button) {
+diff --git a/app/views/editors/ColorSelector.js b/app/views/editors/ColorSelector.js
+index 39f93f1..bb5583f 100644
+--- a/app/views/editors/ColorSelector.js
++++ b/app/views/editors/ColorSelector.js
+@@ -193,14 +193,14 @@ function ColorSelector() {
+ }
+ this.recentlyUsedColor.addEventListener("click", colorListSelectHandler, false);
+ this.documentPaletteContainer.addEventListener("click", colorListSelectHandler, false);
+-
++
+ this.bind("contextmenu", function (event) {
+ var color = Dom.findUpwardForData(event.target, "_color");
+ if (!color) return;
+
+ ColorSelector._handlePaletteMenu(thiz, color, event);
+ }, this.documentPaletteContainer);
+-
++
+ this.bind("click", this.pickColor, this.pickerButton);
+ this.bind("click", this.addToPalette, this.addToPaletteButton);
+ }
+@@ -235,7 +235,7 @@ ColorSelector._handlePaletteMenu = function (thiz, color, event) {
+ }
+ });
+ }
+-
++
+ ColorSelector._colorToRemove = color;
+ ColorSelector._instanceForMenu = thiz;
+ ColorSelector._paletteMenu.showMenuAt(event.clientX, event.clientY);
+@@ -354,7 +354,7 @@ ColorSelector.prototype.setupColors = function () {
+ };
+ ColorSelector.prototype.loadRecentlyUsedColors = function () {
+ var colors = Config.get("gridcolorpicker.recentlyUsedColors", "");
+-
++
+ this._lastUsedColors = colors;
+ var c = colors.split(",");
+ this.recentlyUsedColors = c;
+@@ -378,16 +378,16 @@ ColorSelector.prototype.addToPalette = function () {
+ ColorSelector.prototype.loadDocumentColors = function () {
+ Dom.toggleClass(this.documentPalettePane, "NoDocument", Pencil.controller && Pencil.controller.doc ? false : true);
+ if (!Pencil.controller || !Pencil.controller.doc) return;
+-
++
+ Dom.setInnerText(this.paletteTitle, (Pencil.controller.doc.name || "Untitled Document") + " color palette:")
+-
++
+ var colors = Pencil.controller.getDocumentColorPalette();
+ if (!colors || colors.length == 0) {
+ Dom.addClass(this.documentPalettePane, "Empty");
+ } else {
+ Dom.removeClass(this.documentPalettePane, "Empty");
+ }
+-
++
+ Dom.empty(this.documentPaletteContainer);
+ colors.forEach(function (c) {
+ var cell = document.createElement("div");
+@@ -543,20 +543,20 @@ ColorSelector.installGlobalListeners = function () {
+ ColorSelector.currentPickerInstance.onColorPickingCanceled();
+ return;
+ }
+-
++
+ var maxWidth = 0;
+ var maxHeight = 0;
+ displays.forEach(function (d) {
+ maxWidth = Math.max(maxWidth, d.bounds.x + d.bounds.width);
+ maxHeight = Math.max(maxHeight, d.bounds.y + d.bounds.height);
+ });
+-
++
+
+ document.body.setAttribute("color-picker-active", "picking");
+-
++
+ var x = event.screenX;
+ var y = event.screenY;
+-
++
+ new Capturer().captureFullScreenData(
+ {
+ x: 0,
+@@ -574,7 +574,7 @@ ColorSelector.installGlobalListeners = function () {
+ ColorSelector.currentPickerInstance.onColorPickingCanceled();
+ return;
+ }
+-
++
+ ColorSelector.currentPickerInstance.onColorPicked(color);
+ ColorSelector.currentPickerInstance = null;
+ });
+@@ -611,15 +611,17 @@ ColorSelector.installGlobalListeners = function () {
+ };
+ ColorSelector.prototype.onColorPickingCanceled = function () {
+ document.body.removeAttribute("color-picker-active");
++ document.body.removeAttribute("color-picker-active-external");
+ BaseWidget.closableProcessingDisabled = false;
+ ColorSelector.currentPickerInstance = null;
+ BaseWidget.unregisterClosable(ColorSelector._pickerClosable);
+ };
+ ColorSelector.prototype.onColorPicked = function (color) {
+ document.body.removeAttribute("color-picker-active");
++ document.body.removeAttribute("color-picker-active-external");
+ BaseWidget.closableProcessingDisabled = false;
+ BaseWidget.unregisterClosable(ColorSelector._pickerClosable);
+-
++
+ var a = this.color.a;
+ this.color = Color.fromString(color);
+ this.color.a = a;
+@@ -633,7 +635,42 @@ ColorSelector._pickerClosable = {
+ }
+ };
+
+-ColorSelector.prototype.pickColor = function () {
++ColorSelector.CONFIG_EXTERNAL_COLOR_PICKER_PATH = Config.define("color.external_picker_path", "");
++
++ColorSelector.prototype.pickColor = function (event) {
++ var externalPickerPath = Config.get(ColorSelector.CONFIG_EXTERNAL_COLOR_PICKER_PATH, "");
++ if (!externalPickerPath) {
++ this.pickColorUsingElectronCapture();
++ return;
++ }
++
++ var thiz = this;
++
++ if (event && event.shiftKey) {
++ window.setTimeout(function () {
++ thiz.pickColorUsingExternalTool(externalPickerPath);
++ }, 2000);
++ } else {
++ thiz.pickColorUsingExternalTool(externalPickerPath);
++ }
++};
++ColorSelector.prototype.pickColorUsingExternalTool = function (externalPickerPath) {
++ var thiz = this;
++
++ document.body.setAttribute("color-picker-active-external", true);
++ var exec = require("child_process").exec;
++ exec(externalPickerPath, function (error, stdout, stderr) {
++ var color = stdout;
++ console.log("Color: " + color);
++ if (color) {
++ thiz.onColorPicked(color.replace(/[^0-9#A-F]+/gi, ""));
++ } else {
++ thiz.onColorPickingCanceled();
++ }
++ });
++}
++
++ColorSelector.prototype.pickColorUsingElectronCapture = function () {
+ ColorSelector.installGlobalListeners();
+
+ document.body.setAttribute("color-picker-active", true);
+@@ -641,4 +678,4 @@ ColorSelector.prototype.pickColor = function () {
+
+ ColorSelector.currentPickerInstance = this;
+ BaseWidget.registerClosable(ColorSelector._pickerClosable);
+-};
+\ No newline at end of file
++};
+diff --git a/app/views/editors/ColorSelector.xhtml b/app/views/editors/ColorSelector.xhtml
+index e46c0c7..51afcb3 100644
+--- a/app/views/editors/ColorSelector.xhtml
++++ b/app/views/editors/ColorSelector.xhtml
+@@ -147,7 +147,8 @@
+ margin-left: 0.2ex;
+ }
+
+- body[color-picker-active] .ColorPopup {
++ body[color-picker-active] .ColorPopup,
++ body[color-picker-active-external] .ColorPopup {
+ visibility: hidden !important;
+ }
+ body[color-picker-active="true"],
+diff --git a/app/views/editors/FontEditor.js b/app/views/editors/FontEditor.js
+index f0fdfc6..52fb49e 100644
+--- a/app/views/editors/FontEditor.js
++++ b/app/views/editors/FontEditor.js
+@@ -37,14 +37,14 @@ FontEditor._loadFontItems = function (fontCombo, withNullValue) {
+ FontEditor.prototype.setup = function () {
+ var thiz = this;
+ FontEditor._setupFontCombo(this.fontCombo, function () {
+- thiz.fireChangeEvent();
++ thiz.fireChangeEvent(Font.FAMILY);
+ });
+
+ this.bind("p:ItemSelected", this.invalidateWeightCombo, this.fontCombo);
+
+ this.pixelFontSize.addEventListener("input", function(event) {
+ if (!thiz.font || OnScreenTextEditor.isEditing || thiz.pixelFontSize.value == "" || thiz.pixelFontSize.value < 5) return;
+- thiz.fireChangeEvent();
++ thiz.fireChangeEvent(Font.SIZE);
+ }, false);
+ this.pixelFontSize.addEventListener("keyup", function(event) {
+ if (event.keyCode == 13 || event.keyCode == 10) {
+@@ -52,19 +52,19 @@ FontEditor.prototype.setup = function () {
+ if (thiz.pixelFontSize.value == "" || thiz.pixelFontSize.value < 5) {
+ thiz.pixelFontSize.value = 5;
+ }
+- thiz.fireChangeEvent();
++ thiz.fireChangeEvent(Font.SIZE);
+ }
+ }, false);
+ this.pixelFontSize.addEventListener("wheel", function(event) {
+ if (!thiz.font || OnScreenTextEditor.isEditing || thiz.pixelFontSize.value == "" || thiz.pixelFontSize.value < 5) return;
+- thiz.fireChangeEvent();
++ thiz.fireChangeEvent(Font.SIZE);
+ });
+ this.pixelFontSize.addEventListener("change", function(event) {
+ if (!thiz.font || OnScreenTextEditor.isEditing) return;
+ if (thiz.pixelFontSize.value == "" || thiz.pixelFontSize.value < 5) {
+ thiz.pixelFontSize.value = 5;
+ }
+- thiz.fireChangeEvent();
++ thiz.fireChangeEvent(Font.SIZE);
+ }, false);
+
+ this.boldButton.addEventListener("click", function(event) {
+@@ -76,12 +76,12 @@ FontEditor.prototype.setup = function () {
+ thiz.boldButton.setAttribute("checked", "true");
+ checked = true;
+ }
+- thiz.fireChangeEvent();
++ thiz.fireChangeEvent(Font.WEIGHT);
+ }, false);
+
+ this.bind("p:ItemSelected", function () {
+ if (!thiz.font || OnScreenTextEditor.isEditing) return;
+- thiz.fireChangeEvent();
++ thiz.fireChangeEvent(Font.WEIGHT);
+ }, this.weightCombo);
+
+ this.italicButton.addEventListener("click", function(event) {
+@@ -94,7 +94,7 @@ FontEditor.prototype.setup = function () {
+ checked = true;
+ }
+
+- thiz.fireChangeEvent();
++ thiz.fireChangeEvent(Font.STYLE);
+ }, false);
+
+ this.lineHeight.addEventListener("keyup", function(event) {
+@@ -103,19 +103,19 @@ FontEditor.prototype.setup = function () {
+ if (thiz.lineHeight.value == "" || thiz.lineHeight.value < 0) {
+ thiz.lineHeight.value = 0;
+ }
+- thiz.fireChangeEvent();
++ thiz.fireChangeEvent(Font.WEIGHT);
+ }
+ }, false);
+ this.lineHeight.addEventListener("wheel", function(event) {
+ if (!thiz.font || OnScreenTextEditor.isEditing || thiz.lineHeight.value == "" || thiz.lineHeight.value < 0) return;
+- thiz.fireChangeEvent();
++ thiz.fireChangeEvent(Font.LINE_HEIGHT);
+ });
+ this.lineHeight.addEventListener("change", function(event) {
+ if (!thiz.font || OnScreenTextEditor.isEditing) return;
+ if (thiz.lineHeight.value == "" || thiz.lineHeight.value < 0) {
+ thiz.lineHeight.value = 0;
+ }
+- thiz.fireChangeEvent();
++ thiz.fireChangeEvent(Font.LINE_HEIGHT);
+ }, false);
+
+
+diff --git a/app/views/editors/OnMenuEditor.js b/app/views/editors/OnMenuEditor.js
+index 3b1a307..19fde9e 100644
+--- a/app/views/editors/OnMenuEditor.js
++++ b/app/views/editors/OnMenuEditor.js
+@@ -132,7 +132,8 @@ OnMenuEditor.prototype.generateMenuItems = function () {
+ onDone: function (newImageData, options) {
+ thiz.targetObject.setProperty(propName, newImageData);
+ if (options && options.updateBox) {
+- var dim = new Dimension(newImageData.w, newImageData.h);
++ var ratio = window.devicePixelRatio || 1;
++ var dim = new Dimension(Math.round(newImageData.w / ratio), Math.round(newImageData.h / ratio));
+ thiz.targetObject.setProperty("box", dim);
+ }
+ }
+diff --git a/app/views/editors/PropertyEditor.js b/app/views/editors/PropertyEditor.js
+index 9e83182..0a76264 100644
+--- a/app/views/editors/PropertyEditor.js
++++ b/app/views/editors/PropertyEditor.js
+@@ -4,7 +4,7 @@ function PropertyEditor() {
+ }
+ __extend(BaseTemplatedWidget, PropertyEditor);
+
+-PropertyEditor.prototype.fireChangeEvent = function () {
++PropertyEditor.prototype.fireChangeEvent = function (mask) {
+ this.modified = true;
+- Dom.emitEvent("p:ValueChanged", this.node(), {});
++ Dom.emitEvent("p:ValueChanged", this.node(), {mask: mask});
+ };
+diff --git a/app/views/editors/ShadowStyleEditor.js b/app/views/editors/ShadowStyleEditor.js
+index 2fc5512..000fc7b 100644
+--- a/app/views/editors/ShadowStyleEditor.js
++++ b/app/views/editors/ShadowStyleEditor.js
+@@ -27,7 +27,7 @@ ShadowStyleEditor.prototype.setup = function () {
+ this.selector.addEventListener("ValueChange", function (event) {
+ thiz.color = thiz.selector.getColor().toRGBString();
+ thiz.invalidateColorDisplay();
+- thiz.fireChangeEvent();
++ thiz.fireChangeEvent(ShadowStyle.COLOR);
+ }, false);
+
+ this.selector.addEventListener("p:CloseColorSelector", function (event) {
+@@ -37,8 +37,17 @@ ShadowStyleEditor.prototype.setup = function () {
+ }
+ }, false);
+
+- this.node().addEventListener("change", function (event) {
+- thiz.fireChangeEvent();
++ this.dx.addEventListener("change", function (event) {
++ thiz.fireChangeEvent(ShadowStyle.DX);
++ }, false);
++ this.dy.addEventListener("change", function (event) {
++ thiz.fireChangeEvent(ShadowStyle.DY);
++ }, false);
++ this.size.addEventListener("change", function (event) {
++ thiz.fireChangeEvent(ShadowStyle.SIZE);
++ }, false);
++ this.opacity.addEventListener("change", function (event) {
++ thiz.fireChangeEvent(ShadowStyle.OPACITY);
+ }, false);
+ };
+ ShadowStyleEditor.prototype.setValue = function (shadowStyle) {
+diff --git a/app/views/editors/SharedBorderStyleEditor.js b/app/views/editors/SharedBorderStyleEditor.js
+index ccc5c72..72c410e 100644
+--- a/app/views/editors/SharedBorderStyleEditor.js
++++ b/app/views/editors/SharedBorderStyleEditor.js
+@@ -14,10 +14,10 @@ SharedBorderStyleEditor.prototype.setup = function () {
+
+ var thiz = this;
+ this.editor.addEventListener("p:ItemSelected", function (event) {
+- thiz.handleCommandEvent();
++ thiz.handleCommandEvent(StrokeStyle.ARRAY);
+ }, false);
+ this.editor.addEventListener("input", function (event) {
+- thiz.handleCommandEvent();
++ thiz.handleCommandEvent(StrokeStyle.W);
+ }, false);
+
+ this.editor.addEventListener("keypress", function (event) {
+@@ -27,11 +27,11 @@ SharedBorderStyleEditor.prototype.setup = function () {
+ }, false);
+
+ };
+-SharedBorderStyleEditor.prototype.handleCommandEvent = function () {
++SharedBorderStyleEditor.prototype.handleCommandEvent = function (mask) {
+ var thiz = this;
+ var style = thiz.editor.getValue();
+ Pencil.activeCanvas.run(function () {
+- this.setProperty(SharedBorderStyleEditor.PROPERTY_NAME, thiz.editor.getValue());
++ this.setProperty(SharedBorderStyleEditor.PROPERTY_NAME, thiz.editor.getValue(), false, mask);
+ Pencil.activeCanvas.snappingHelper.updateSnappingGuide(this);
+ thiz.invalidate();
+ Pencil.activeCanvas.invalidateEditors(thiz);
+diff --git a/app/views/editors/SharedFontEditor.js b/app/views/editors/SharedFontEditor.js
+index b9f493e..d5df4af 100644
+--- a/app/views/editors/SharedFontEditor.js
++++ b/app/views/editors/SharedFontEditor.js
+@@ -20,7 +20,7 @@ SharedFontEditor.prototype.setup = function () {
+ FontEditor._setupFontCombo(this.fontCombo, function(event) {
+ if (!thiz.target || !thiz.font || OnScreenTextEditor.isEditing) return;
+ thiz.font.family = thiz.fontCombo.getSelectedItem().family;
+- thiz._applyValue();
++ thiz._applyValue(Font.FAMILY);
+ });
+
+ this.bind("p:ItemSelected", this.invalidateWeightCombo, this.fontCombo);
+@@ -28,20 +28,20 @@ SharedFontEditor.prototype.setup = function () {
+ this.pixelFontSize.addEventListener("input", function(event) {
+ if (!thiz.target || !thiz.font || OnScreenTextEditor.isEditing || thiz.pixelFontSize.value == "" || thiz.pixelFontSize.value == "0") return;
+ thiz.font.size = thiz.pixelFontSize.value + "px";
+- thiz._applyValue();
++ thiz._applyValue(Font.SIZE);
+ }, false);
+
+ this.pixelFontSize.addEventListener("wheel", function(event) {
+ if (!thiz.target || !thiz.font || OnScreenTextEditor.isEditing || thiz.pixelFontSize.value == "") return;
+ thiz.font.size = thiz.pixelFontSize.value + "px";
+- thiz._applyValue();
++ thiz._applyValue(Font.SIZE);
+ });
+ this.pixelFontSize.addEventListener("keyup", function(event) {
+ if (event.keyCode == 13 || event.keyCode == 10) {
+ if (!thiz.target || !thiz.font || OnScreenTextEditor.isEditing || thiz.pixelFontSize.value == "") return;
+ thiz.pixelFontSize.value = Math.max(5, parseInt(thiz.pixelFontSize.value, 10));
+ thiz.font.size = thiz.pixelFontSize.value + "px";
+- thiz._applyValue();
++ thiz._applyValue(Font.SIZE);
+ }
+ }, false);
+ this.pixelFontSize.addEventListener("change", function(event) {
+@@ -60,13 +60,13 @@ SharedFontEditor.prototype.setup = function () {
+ checked = true;
+ }
+ thiz.font.weight = checked ? "bold" : "normal";
+- thiz._applyValue();
++ thiz._applyValue(Font.WEIGHT);
+ }, false);
+
+ this.bind("p:ItemSelected", function () {
+ if (!thiz.target || !thiz.font || OnScreenTextEditor.isEditing) return;
+ thiz.font.weight = thiz.weightCombo.getSelectedItem();
+- thiz._applyValue();
++ thiz._applyValue(Font.WEIGHT);
+
+ }, this.weightCombo);
+
+@@ -82,7 +82,7 @@ SharedFontEditor.prototype.setup = function () {
+ }
+
+ thiz.font.style = checked ? "italic" : "normal";
+- thiz._applyValue();
++ thiz._applyValue(Font.STYLE);
+ }, false);
+
+ this.formatPainterButton.addEventListener("click", function (event) {
+@@ -139,10 +139,10 @@ SharedFontEditor.prototype.beginFormatPainter = function () {
+ SharedFontEditor.prototype.isDisabled = function () {
+ return this.disabledEditor;
+ };
+-SharedFontEditor.prototype._applyValue = function () {
++SharedFontEditor.prototype._applyValue = function (attrMask) {
+ var thiz = this;
+ Pencil.activeCanvas.run(function() {
+- this.setProperty(SharedFontEditor.PROPERTY_NAME, thiz.font);
++ this.setProperty(SharedFontEditor.PROPERTY_NAME, thiz.font, false, attrMask);
+ }, this.target, Util.getMessage("action.apply.properties.value"))
+ };
+ SharedFontEditor.prototype.attach = function (target) {
+diff --git a/app/views/editors/SharedPropertyEditor.js b/app/views/editors/SharedPropertyEditor.js
+index 81226ec..62b8388 100644
+--- a/app/views/editors/SharedPropertyEditor.js
++++ b/app/views/editors/SharedPropertyEditor.js
+@@ -18,7 +18,7 @@ SharedPropertyEditor.prototype.setup = function () {
+ if (!editor) return;
+
+ var propertyName = editor._property.name;
+- thiz.target.setProperty(propertyName, thiz.propertyEditor[propertyName].getValue());
++ thiz.target.setProperty(propertyName, thiz.propertyEditor[propertyName].getValue(), false, event.mask);
+
+ thiz.validationEditorUI();
+ }, false);
+diff --git a/app/views/editors/StrokeEditor.js b/app/views/editors/StrokeEditor.js
+index 69179d0..7a03497 100644
+--- a/app/views/editors/StrokeEditor.js
++++ b/app/views/editors/StrokeEditor.js
+@@ -61,11 +61,11 @@ StrokeEditor.prototype.setup = function () {
+ this.styleCombo.setItems(strokeItems);
+ var thiz = this;
+ this.styleCombo.addEventListener("p:ItemSelected", function (event) {
+- thiz.fireChangeEvent();
++ thiz.fireChangeEvent(StrokeStyle.ARRAY);
+ }, false);
+ this.strokeWidth.addEventListener("input", function (event) {
+ if (thiz.strokeWidth.value == "") thiz.strokeWidth.value = 1;
+- thiz.fireChangeEvent();
++ thiz.fireChangeEvent(StrokeStyle.W);
+ }, false);
+
+ };
+diff --git a/app/views/menus/MainMenu.js b/app/views/menus/MainMenu.js
+index 6caddec..8007da2 100644
+--- a/app/views/menus/MainMenu.js
++++ b/app/views/menus/MainMenu.js
+@@ -194,15 +194,15 @@ MainMenu.prototype.setup = function () {
+ }
+ }));
+
+- // developerToolSubItems.push(UICommandManager.register({
+- // key: "deployStencilCollection",
+- // label: "Deploy Stencil Collection...",
+- // shortcut: "Ctrl+Shift+D",
+- // isAvailable: function () { return Pencil.controller && Pencil.controller.doc; },
+- // run: function () {
+- // new StencilCollectionBuilder(Pencil.controller).deploy();
+- // }
+- // }));
++ developerToolSubItems.push(UICommandManager.register({
++ key: "deployStencilCollection",
++ label: "Deploy Stencil Collection...",
++ shortcut: "Ctrl+Shift+D",
++ isAvailable: function () { return Pencil.controller && Pencil.controller.doc; },
++ run: function () {
++ new StencilCollectionBuilder(Pencil.controller).deploy();
++ }
++ }));
+
+ developerToolSubItems.push(UICommandManager.register({
+ key: "checkMissingResources",
+diff --git a/app/views/tools/OpenClipartPane.js b/app/views/tools/OpenClipartPane.js
+index 9e34d54..3a2683a 100644
+--- a/app/views/tools/OpenClipartPane.js
++++ b/app/views/tools/OpenClipartPane.js
+@@ -1,7 +1,7 @@
+ function OpenClipartPane() {
+ BaseTemplatedWidget.call(this);
+ var thiz = this;
+- this.backend = new OpenClipartSearch();
++ this.backend = new OpenClipartSearch2();
+
+ function injectSvgInfo (svg) {
+ try {
+diff --git a/app/views/tools/ScriptEditorDialog.js b/app/views/tools/ScriptEditorDialog.js
+index 591d79c..b2eae60 100644
+--- a/app/views/tools/ScriptEditorDialog.js
++++ b/app/views/tools/ScriptEditorDialog.js
+@@ -1,6 +1,10 @@
+-function ScriptEditorDialog() {
++function ScriptEditorDialog(large) {
+ Dialog.call(this);
+ this.title = "Script Editor";
++
++ if (large) {
++ Dom.addClass(this.scriptInputContainer, "Large");
++ }
+ }
+ __extend(Dialog, ScriptEditorDialog);
+
+diff --git a/app/views/tools/ScriptEditorDialog.xhtml b/app/views/tools/ScriptEditorDialog.xhtml
+index 8276528..4b617e3 100644
+--- a/app/views/tools/ScriptEditorDialog.xhtml
++++ b/app/views/tools/ScriptEditorDialog.xhtml
+@@ -17,6 +17,10 @@
+ height: 40em;
+ width: 60em;
+ }
++ @scriptInputContainer.Large {
++ height: 55em;
++ width: 100em;
++ }
+
+ </style>
+ <hbox flex="1" anon-id="scriptInputContainer">
+diff --git a/app/views/tools/StencilGeneratorDialog.js b/app/views/tools/StencilGeneratorDialog.js
+index 6610dab..aff5834 100644
+--- a/app/views/tools/StencilGeneratorDialog.js
++++ b/app/views/tools/StencilGeneratorDialog.js
+@@ -241,7 +241,7 @@ StencilGeneratorDialog.prototype.loadStencil = function (result, stencils, index
+ try {
+ var readOnDone = function (fileData) {
+ //var data = Base64.encode(fileData, true);
+- var data = new Buffer(fileData).toString("base64");
++ var data = Buffer.from(fileData).toString("base64");
+ var st = {
+ id: "img_" + _index,
+ label: _stencils[_index]._label,
+diff --git a/package-lock.json b/package-lock.json
+index d07ef39..89df0d2 100644
+--- a/package-lock.json
++++ b/package-lock.json
+@@ -9,10 +9,96 @@
+ "integrity": "sha512-XtGk+IF57pr852UK1AhQJXqmm1WmSgS5uISL+LPs0z/iAxXouMvdlLJrHPeukP6gd7yR2rDTMSMkHNODgwIq7A==",
+ "dev": true
+ },
++ "@electron/get": {
++ "version": "1.12.2",
++ "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.12.2.tgz",
++ "integrity": "sha512-vAuHUbfvBQpYTJ5wB7uVIDq5c/Ry0fiTBMs7lnEYAo/qXXppIVcWdfBr57u6eRnKdVso7KSiH6p/LbQAG6Izrg==",
++ "dev": true,
++ "requires": {
++ "debug": "^4.1.1",
++ "env-paths": "^2.2.0",
++ "fs-extra": "^8.1.0",
++ "global-agent": "^2.0.2",
++ "global-tunnel-ng": "^2.7.1",
++ "got": "^9.6.0",
++ "progress": "^2.0.3",
++ "sanitize-filename": "^1.6.2",
++ "sumchecker": "^3.0.1"
++ },
++ "dependencies": {
++ "debug": {
++ "version": "4.2.0",
++ "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
++ "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
++ "dev": true,
++ "requires": {
++ "ms": "2.1.2"
++ }
++ },
++ "get-stream": {
++ "version": "4.1.0",
++ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
++ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
++ "dev": true,
++ "requires": {
++ "pump": "^3.0.0"
++ }
++ },
++ "got": {
++ "version": "9.6.0",
++ "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
++ "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==",
++ "dev": true,
++ "requires": {
++ "@sindresorhus/is": "^0.14.0",
++ "@szmarczak/http-timer": "^1.1.2",
++ "cacheable-request": "^6.0.0",
++ "decompress-response": "^3.3.0",
++ "duplexer3": "^0.1.4",
++ "get-stream": "^4.1.0",
++ "lowercase-keys": "^1.0.1",
++ "mimic-response": "^1.0.1",
++ "p-cancelable": "^1.0.0",
++ "to-readable-stream": "^1.0.0",
++ "url-parse-lax": "^3.0.0"
++ }
++ },
++ "prepend-http": {
++ "version": "2.0.0",
++ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
++ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
++ "dev": true
++ },
++ "url-parse-lax": {
++ "version": "3.0.0",
++ "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
++ "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=",
++ "dev": true,
++ "requires": {
++ "prepend-http": "^2.0.0"
++ }
++ }
++ }
++ },
++ "@sindresorhus/is": {
++ "version": "0.14.0",
++ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
++ "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==",
++ "dev": true
++ },
++ "@szmarczak/http-timer": {
++ "version": "1.1.2",
++ "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
++ "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==",
++ "dev": true,
++ "requires": {
++ "defer-to-connect": "^1.0.1"
++ }
++ },
+ "@types/node": {
+- "version": "10.14.15",
+- "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.15.tgz",
+- "integrity": "sha512-CBR5avlLcu0YCILJiDIXeU2pTw7UK/NIxfC63m7d7CVamho1qDEzXKkOtEauQRPMy6MI8mLozth+JJkas7HY6g==",
++ "version": "12.19.2",
++ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.2.tgz",
++ "integrity": "sha512-SRH6QM0IMOBBFmDiJ75vlhcbUEYEquvSuhsVW9ijG20JvdFTfOrB1p6ddZxz5y/JNnbf+9HoHhjhOVSX2hsJyA==",
+ "dev": true
+ },
+ "abbrev": {
+@@ -208,12 +294,6 @@
+ "sprintf-js": "~1.0.2"
+ }
+ },
+- "array-find-index": {
+- "version": "1.0.2",
+- "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+- "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
+- "dev": true
+- },
+ "asn1": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+@@ -289,6 +369,13 @@
+ "bluebird": "^3.5.5"
+ }
+ },
++ "boolean": {
++ "version": "3.0.1",
++ "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.1.tgz",
++ "integrity": "sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==",
++ "dev": true,
++ "optional": true
++ },
+ "boxen": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
+@@ -369,6 +456,12 @@
+ "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
+ "dev": true
+ },
++ "buffer-crc32": {
++ "version": "0.2.13",
++ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
++ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
++ "dev": true
++ },
+ "buffer-fill": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
+@@ -437,20 +530,36 @@
+ }
+ }
+ },
+- "camelcase": {
+- "version": "2.1.1",
+- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+- "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+- "dev": true
+- },
+- "camelcase-keys": {
+- "version": "2.1.0",
+- "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+- "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
++ "cacheable-request": {
++ "version": "6.1.0",
++ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz",
++ "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==",
+ "dev": true,
+ "requires": {
+- "camelcase": "^2.0.0",
+- "map-obj": "^1.0.0"
++ "clone-response": "^1.0.2",
++ "get-stream": "^5.1.0",
++ "http-cache-semantics": "^4.0.0",
++ "keyv": "^3.0.0",
++ "lowercase-keys": "^2.0.0",
++ "normalize-url": "^4.1.0",
++ "responselike": "^1.0.2"
++ },
++ "dependencies": {
++ "get-stream": {
++ "version": "5.2.0",
++ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
++ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
++ "dev": true,
++ "requires": {
++ "pump": "^3.0.0"
++ }
++ },
++ "lowercase-keys": {
++ "version": "2.0.0",
++ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
++ "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
++ "dev": true
++ }
+ }
+ },
+ "capture-stack-trace": {
+@@ -566,6 +675,15 @@
+ "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=",
+ "dev": true
+ },
++ "clone-response": {
++ "version": "1.0.2",
++ "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
++ "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
++ "dev": true,
++ "requires": {
++ "mimic-response": "^1.0.0"
++ }
++ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+@@ -617,51 +735,24 @@
+ "concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+- "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=",
++ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+- },
+- "dependencies": {
+- "isarray": {
+- "version": "1.0.0",
+- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+- "dev": true
+- },
+- "readable-stream": {
+- "version": "2.3.6",
+- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+- "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=",
+- "dev": true,
+- "requires": {
+- "core-util-is": "~1.0.0",
+- "inherits": "~2.0.3",
+- "isarray": "~1.0.0",
+- "process-nextick-args": "~2.0.0",
+- "safe-buffer": "~5.1.1",
+- "string_decoder": "~1.1.1",
+- "util-deprecate": "~1.0.1"
+- }
+- },
+- "safe-buffer": {
+- "version": "5.1.2",
+- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+- "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=",
+- "dev": true
+- },
+- "string_decoder": {
+- "version": "1.1.1",
+- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+- "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
+- "dev": true,
+- "requires": {
+- "safe-buffer": "~5.1.0"
+- }
+- }
++ }
++ },
++ "config-chain": {
++ "version": "1.1.12",
++ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz",
++ "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==",
++ "dev": true,
++ "optional": true,
++ "requires": {
++ "ini": "^1.3.4",
++ "proto-list": "~1.2.1"
+ }
+ },
+ "configstore": {
+@@ -684,6 +775,13 @@
+ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+ "dev": true
+ },
++ "core-js": {
++ "version": "3.6.5",
++ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
++ "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==",
++ "dev": true,
++ "optional": true
++ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+@@ -734,15 +832,6 @@
+ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=",
+ "dev": true
+ },
+- "currently-unhandled": {
+- "version": "0.4.1",
+- "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+- "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+- "dev": true,
+- "requires": {
+- "array-find-index": "^1.0.1"
+- }
+- },
+ "dashdash": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+@@ -775,6 +864,15 @@
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true
+ },
++ "decompress-response": {
++ "version": "3.3.0",
++ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
++ "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
++ "dev": true,
++ "requires": {
++ "mimic-response": "^1.0.0"
++ }
++ },
+ "deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+@@ -790,6 +888,22 @@
+ "clone": "^1.0.2"
+ }
+ },
++ "defer-to-connect": {
++ "version": "1.1.3",
++ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz",
++ "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==",
++ "dev": true
++ },
++ "define-properties": {
++ "version": "1.1.3",
++ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
++ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
++ "dev": true,
++ "optional": true,
++ "requires": {
++ "object-keys": "^1.0.12"
++ }
++ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+@@ -808,6 +922,13 @@
+ "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
+ "dev": true
+ },
++ "detect-node": {
++ "version": "2.0.4",
++ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
++ "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
++ "dev": true,
++ "optional": true
++ },
+ "dmg-builder": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-5.3.1.tgz",
+@@ -868,13 +989,13 @@
+ "dev": true
+ },
+ "electron": {
+- "version": "6.0.1",
+- "resolved": "https://registry.npmjs.org/electron/-/electron-6.0.1.tgz",
+- "integrity": "sha512-XY69rI5IThIxsOS2BD+1ZkHE9hqkm4xN5a3WQFSmFRr2by4q5CnIe9vXmptlouGPTLs3tb7ySX/+K9CvH3szvg==",
++ "version": "10.1.5",
++ "resolved": "https://registry.npmjs.org/electron/-/electron-10.1.5.tgz",
++ "integrity": "sha512-fys/KnEfJq05TtMij+lFvLuKkuVH030CHYx03iZrW5DNNLwjE6cW3pysJ420lB0FRSfPjTHBMu2eVCf5TG71zQ==",
+ "dev": true,
+ "requires": {
+- "@types/node": "^10.12.18",
+- "electron-download": "^4.1.0",
++ "@electron/get": "^1.0.1",
++ "@types/node": "^12.0.12",
+ "extract-zip": "^1.0.3"
+ }
+ },
+@@ -1047,34 +1168,6 @@
+ }
+ }
+ },
+- "electron-download": {
+- "version": "4.1.1",
+- "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz",
+- "integrity": "sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==",
+- "dev": true,
+- "requires": {
+- "debug": "^3.0.0",
+- "env-paths": "^1.0.0",
+- "fs-extra": "^4.0.1",
+- "minimist": "^1.2.0",
+- "nugget": "^2.0.1",
+- "path-exists": "^3.0.0",
+- "rc": "^1.2.1",
+- "semver": "^5.4.1",
+- "sumchecker": "^2.0.2"
+- },
+- "dependencies": {
+- "debug": {
+- "version": "3.2.6",
+- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+- "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+- "dev": true,
+- "requires": {
+- "ms": "^2.1.1"
+- }
+- }
+- }
+- },
+ "electron-osx-sign": {
+ "version": "0.4.10",
+ "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.10.tgz",
+@@ -1162,6 +1255,13 @@
+ "integrity": "sha1-kzoEBShgyF6DwSJHnEdIqOTHIVY=",
+ "dev": true
+ },
++ "encodeurl": {
++ "version": "1.0.2",
++ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
++ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
++ "dev": true,
++ "optional": true
++ },
+ "end-of-stream": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
+@@ -1172,19 +1272,17 @@
+ }
+ },
+ "env-paths": {
+- "version": "1.0.0",
+- "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz",
+- "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=",
++ "version": "2.2.0",
++ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz",
++ "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==",
+ "dev": true
+ },
+- "error-ex": {
+- "version": "1.3.2",
+- "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+- "integrity": "sha1-tKxAZIEH/c3PriQvQovqihTU8b8=",
++ "es6-error": {
++ "version": "4.1.1",
++ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
++ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
+ "dev": true,
+- "requires": {
+- "is-arrayish": "^0.2.1"
+- }
++ "optional": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+@@ -1220,15 +1318,32 @@
+ "dev": true
+ },
+ "extract-zip": {
+- "version": "1.6.7",
+- "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz",
+- "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=",
++ "version": "1.7.0",
++ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz",
++ "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==",
+ "dev": true,
+ "requires": {
+- "concat-stream": "1.6.2",
+- "debug": "2.6.9",
+- "mkdirp": "0.5.1",
+- "yauzl": "2.4.1"
++ "concat-stream": "^1.6.2",
++ "debug": "^2.6.9",
++ "mkdirp": "^0.5.4",
++ "yauzl": "^2.10.0"
++ },
++ "dependencies": {
++ "minimist": {
++ "version": "1.2.5",
++ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
++ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
++ "dev": true
++ },
++ "mkdirp": {
++ "version": "0.5.5",
++ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
++ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
++ "dev": true,
++ "requires": {
++ "minimist": "^1.2.5"
++ }
++ }
+ }
+ },
+ "extsprintf": {
+@@ -1250,35 +1365,14 @@
+ "dev": true
+ },
+ "fd-slicer": {
+- "version": "1.0.1",
+- "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
+- "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
++ "version": "1.1.0",
++ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
++ "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
+ "dev": true,
+ "requires": {
+ "pend": "~1.2.0"
+ }
+ },
+- "find-up": {
+- "version": "1.1.2",
+- "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+- "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+- "dev": true,
+- "requires": {
+- "path-exists": "^2.0.0",
+- "pinkie-promise": "^2.0.0"
+- },
+- "dependencies": {
+- "path-exists": {
+- "version": "2.1.0",
+- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+- "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+- "dev": true,
+- "requires": {
+- "pinkie-promise": "^2.0.0"
+- }
+- }
+- }
+- },
+ "forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+@@ -1297,12 +1391,12 @@
+ }
+ },
+ "fs-extra": {
+- "version": "4.0.3",
+- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
+- "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
++ "version": "8.1.0",
++ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
++ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "requires": {
+- "graceful-fs": "^4.1.2",
++ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+@@ -1367,12 +1461,6 @@
+ "integrity": "sha1-T5RBKoLbMvNuOwuXQfipf+sDH34=",
+ "dev": true
+ },
+- "get-stdin": {
+- "version": "4.0.1",
+- "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+- "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
+- "dev": true
+- },
+ "get-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+@@ -1402,6 +1490,31 @@
+ "path-is-absolute": "^1.0.0"
+ }
+ },
++ "global-agent": {
++ "version": "2.1.12",
++ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.1.12.tgz",
++ "integrity": "sha512-caAljRMS/qcDo69X9BfkgrihGUgGx44Fb4QQToNQjsiWh+YlQ66uqYVAdA8Olqit+5Ng0nkz09je3ZzANMZcjg==",
++ "dev": true,
++ "optional": true,
++ "requires": {
++ "boolean": "^3.0.1",
++ "core-js": "^3.6.5",
++ "es6-error": "^4.1.1",
++ "matcher": "^3.0.0",
++ "roarr": "^2.15.3",
++ "semver": "^7.3.2",
++ "serialize-error": "^7.0.1"
++ },
++ "dependencies": {
++ "semver": {
++ "version": "7.3.2",
++ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
++ "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
++ "dev": true,
++ "optional": true
++ }
++ }
++ },
+ "global-dirs": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
+@@ -1411,6 +1524,29 @@
+ "ini": "^1.3.4"
+ }
+ },
++ "global-tunnel-ng": {
++ "version": "2.7.1",
++ "resolved": "https://registry.npmjs.org/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz",
++ "integrity": "sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==",
++ "dev": true,
++ "optional": true,
++ "requires": {
++ "encodeurl": "^1.0.2",
++ "lodash": "^4.17.10",
++ "npm-conf": "^1.1.3",
++ "tunnel": "^0.0.6"
++ }
++ },
++ "globalthis": {
++ "version": "1.0.1",
++ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.1.tgz",
++ "integrity": "sha512-mJPRTc/P39NH/iNG4mXa9aIhNymaQikTrnspeCa2ZuJ+mH2QN/rXwtX3XwKrHqWgUQFbNZKtHM105aHzJalElw==",
++ "dev": true,
++ "optional": true,
++ "requires": {
++ "define-properties": "^1.1.3"
++ }
++ },
+ "got": {
+ "version": "6.7.1",
+ "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
+@@ -1473,6 +1609,12 @@
+ "lru-cache": "^5.1.1"
+ }
+ },
++ "http-cache-semantics": {
++ "version": "4.1.0",
++ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
++ "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
++ "dev": true
++ },
+ "http-signature": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+@@ -1505,15 +1647,6 @@
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true
+ },
+- "indent-string": {
+- "version": "2.1.0",
+- "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+- "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+- "dev": true,
+- "requires": {
+- "repeating": "^2.0.0"
+- }
+- },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+@@ -1542,12 +1675,6 @@
+ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
+ "dev": true
+ },
+- "is-arrayish": {
+- "version": "0.2.1",
+- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+- "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+- "dev": true
+- },
+ "is-ci": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
+@@ -1557,15 +1684,6 @@
+ "ci-info": "^1.5.0"
+ }
+ },
+- "is-finite": {
+- "version": "1.0.2",
+- "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+- "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
+- "dev": true,
+- "requires": {
+- "number-is-nan": "^1.0.0"
+- }
+- },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+@@ -1630,16 +1748,10 @@
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+ "dev": true
+ },
+- "is-utf8": {
+- "version": "0.2.1",
+- "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+- "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
+- "dev": true
+- },
+ "isarray": {
+- "version": "0.0.1",
+- "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+- "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
++ "version": "1.0.0",
++ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
++ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "isbinaryfile": {
+@@ -1679,6 +1791,12 @@
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+ "dev": true
+ },
++ "json-buffer": {
++ "version": "3.0.0",
++ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
++ "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=",
++ "dev": true
++ },
+ "json-schema": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+@@ -1727,6 +1845,15 @@
+ "verror": "1.10.0"
+ }
+ },
++ "keyv": {
++ "version": "3.1.0",
++ "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
++ "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==",
++ "dev": true,
++ "requires": {
++ "json-buffer": "3.0.0"
++ }
++ },
+ "latest-version": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz",
+@@ -1751,19 +1878,6 @@
+ "invert-kv": "^2.0.0"
+ }
+ },
+- "load-json-file": {
+- "version": "1.1.0",
+- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+- "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+- "dev": true,
+- "requires": {
+- "graceful-fs": "^4.1.2",
+- "parse-json": "^2.2.0",
+- "pify": "^2.0.0",
+- "pinkie-promise": "^2.0.0",
+- "strip-bom": "^2.0.0"
+- }
+- },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+@@ -1774,6 +1888,13 @@
+ "path-exists": "^3.0.0"
+ }
+ },
++ "lodash": {
++ "version": "4.17.20",
++ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
++ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
++ "dev": true,
++ "optional": true
++ },
+ "lodash.assign": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
+@@ -1789,16 +1910,6 @@
+ "chalk": "^2.0.1"
+ }
+ },
+- "loud-rejection": {
+- "version": "1.6.0",
+- "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+- "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+- "dev": true,
+- "requires": {
+- "currently-unhandled": "^0.4.1",
+- "signal-exit": "^3.0.0"
+- }
+- },
+ "lowercase-keys": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
+@@ -1840,11 +1951,24 @@
+ "p-defer": "^1.0.0"
+ }
+ },
+- "map-obj": {
+- "version": "1.0.1",
+- "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+- "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
+- "dev": true
++ "matcher": {
++ "version": "3.0.0",
++ "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
++ "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
++ "dev": true,
++ "optional": true,
++ "requires": {
++ "escape-string-regexp": "^4.0.0"
++ },
++ "dependencies": {
++ "escape-string-regexp": {
++ "version": "4.0.0",
++ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
++ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
++ "dev": true,
++ "optional": true
++ }
++ }
+ },
+ "mem": {
+ "version": "4.3.0",
+@@ -1865,24 +1989,6 @@
+ }
+ }
+ },
+- "meow": {
+- "version": "3.7.0",
+- "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+- "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+- "dev": true,
+- "requires": {
+- "camelcase-keys": "^2.0.0",
+- "decamelize": "^1.1.2",
+- "loud-rejection": "^1.0.0",
+- "map-obj": "^1.0.1",
+- "minimist": "^1.1.3",
+- "normalize-package-data": "^2.3.4",
+- "object-assign": "^4.0.1",
+- "read-pkg-up": "^1.0.1",
+- "redent": "^1.0.0",
+- "trim-newlines": "^1.0.0"
+- }
+- },
+ "mime": {
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
+@@ -1910,6 +2016,12 @@
+ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+ "dev": true
+ },
++ "mimic-response": {
++ "version": "1.0.1",
++ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
++ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
++ "dev": true
++ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+@@ -2030,6 +2142,23 @@
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
++ "normalize-url": {
++ "version": "4.5.0",
++ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz",
++ "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==",
++ "dev": true
++ },
++ "npm-conf": {
++ "version": "1.1.3",
++ "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz",
++ "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==",
++ "dev": true,
++ "optional": true,
++ "requires": {
++ "config-chain": "^1.1.11",
++ "pify": "^3.0.0"
++ }
++ },
+ "npm-run-path": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+@@ -2051,21 +2180,6 @@
+ "set-blocking": "~2.0.0"
+ }
+ },
+- "nugget": {
+- "version": "2.0.1",
+- "resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz",
+- "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=",
+- "dev": true,
+- "requires": {
+- "debug": "^2.1.3",
+- "minimist": "^1.1.0",
+- "pretty-bytes": "^1.0.2",
+- "progress-stream": "^1.1.0",
+- "request": "^2.45.0",
+- "single-line-log": "^1.1.2",
+- "throttleit": "0.0.2"
+- }
+- },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+@@ -2085,10 +2199,11 @@
+ "dev": true
+ },
+ "object-keys": {
+- "version": "0.4.0",
+- "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz",
+- "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=",
+- "dev": true
++ "version": "1.1.1",
++ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
++ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
++ "dev": true,
++ "optional": true
+ },
+ "once": {
+ "version": "1.4.0",
+@@ -2211,6 +2326,12 @@
+ "os-tmpdir": "^1.0.0"
+ }
+ },
++ "p-cancelable": {
++ "version": "1.1.0",
++ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz",
++ "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==",
++ "dev": true
++ },
+ "p-defer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
+@@ -2282,15 +2403,6 @@
+ }
+ }
+ },
+- "parse-json": {
+- "version": "2.2.0",
+- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+- "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+- "dev": true,
+- "requires": {
+- "error-ex": "^1.2.0"
+- }
+- },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+@@ -2321,17 +2433,6 @@
+ "integrity": "sha1-1i27VnlAXXLEc37FhgDp3c8G0kw=",
+ "dev": true
+ },
+- "path-type": {
+- "version": "1.1.0",
+- "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+- "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+- "dev": true,
+- "requires": {
+- "graceful-fs": "^4.1.2",
+- "pify": "^2.0.0",
+- "pinkie-promise": "^2.0.0"
+- }
+- },
+ "pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+@@ -2345,25 +2446,11 @@
+ "dev": true
+ },
+ "pify": {
+- "version": "2.3.0",
+- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+- "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+- "dev": true
+- },
+- "pinkie": {
+- "version": "2.0.4",
+- "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+- "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+- "dev": true
+- },
+- "pinkie-promise": {
+- "version": "2.0.1",
+- "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+- "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
++ "version": "3.0.0",
++ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
++ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "dev": true,
+- "requires": {
+- "pinkie": "^2.0.0"
+- }
++ "optional": true
+ },
+ "plist": {
+ "version": "3.0.1",
+@@ -2396,31 +2483,24 @@
+ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
+ "dev": true
+ },
+- "pretty-bytes": {
+- "version": "1.0.4",
+- "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz",
+- "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=",
+- "dev": true,
+- "requires": {
+- "get-stdin": "^4.0.1",
+- "meow": "^3.1.0"
+- }
+- },
+ "process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha1-eCDZsWEgzFXKmud5JoCufbptf+I=",
+ "dev": true
+ },
+- "progress-stream": {
+- "version": "1.2.0",
+- "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz",
+- "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c=",
++ "progress": {
++ "version": "2.0.3",
++ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
++ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
++ "dev": true
++ },
++ "proto-list": {
++ "version": "1.2.4",
++ "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
++ "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=",
+ "dev": true,
+- "requires": {
+- "speedometer": "~0.1.2",
+- "through2": "~0.2.3"
+- }
++ "optional": true
+ },
+ "pseudomap": {
+ "version": "1.0.2",
+@@ -2485,47 +2565,27 @@
+ "lazy-val": "^1.0.3"
+ }
+ },
+- "read-pkg": {
+- "version": "1.1.0",
+- "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+- "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+- "dev": true,
+- "requires": {
+- "load-json-file": "^1.0.0",
+- "normalize-package-data": "^2.3.2",
+- "path-type": "^1.0.0"
+- }
+- },
+- "read-pkg-up": {
+- "version": "1.0.1",
+- "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+- "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+- "dev": true,
+- "requires": {
+- "find-up": "^1.0.0",
+- "read-pkg": "^1.0.0"
+- }
+- },
+ "readable-stream": {
+- "version": "1.1.14",
+- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+- "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
++ "version": "2.3.7",
++ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
++ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+- "inherits": "~2.0.1",
+- "isarray": "0.0.1",
+- "string_decoder": "~0.10.x"
+- }
+- },
+- "redent": {
+- "version": "1.0.0",
+- "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+- "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+- "dev": true,
+- "requires": {
+- "indent-string": "^2.1.0",
+- "strip-indent": "^1.0.1"
++ "inherits": "~2.0.3",
++ "isarray": "~1.0.0",
++ "process-nextick-args": "~2.0.0",
++ "safe-buffer": "~5.1.1",
++ "string_decoder": "~1.1.1",
++ "util-deprecate": "~1.0.1"
++ },
++ "dependencies": {
++ "safe-buffer": {
++ "version": "5.1.2",
++ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
++ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
++ "dev": true
++ }
+ }
+ },
+ "registry-auth-token": {
+@@ -2547,15 +2607,6 @@
+ "rc": "^1.0.1"
+ }
+ },
+- "repeating": {
+- "version": "2.0.1",
+- "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+- "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+- "dev": true,
+- "requires": {
+- "is-finite": "^1.0.0"
+- }
+- },
+ "request": {
+ "version": "2.88.0",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
+@@ -2605,6 +2656,15 @@
+ "path-parse": "^1.0.6"
+ }
+ },
++ "responselike": {
++ "version": "1.0.2",
++ "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz",
++ "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=",
++ "dev": true,
++ "requires": {
++ "lowercase-keys": "^1.0.0"
++ }
++ },
+ "restore-cursor": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+@@ -2624,6 +2684,30 @@
+ "glob": "^7.0.5"
+ }
+ },
++ "roarr": {
++ "version": "2.15.4",
++ "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
++ "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
++ "dev": true,
++ "optional": true,
++ "requires": {
++ "boolean": "^3.0.1",
++ "detect-node": "^2.0.4",
++ "globalthis": "^1.0.1",
++ "json-stringify-safe": "^5.0.1",
++ "semver-compare": "^1.0.0",
++ "sprintf-js": "^1.1.2"
++ },
++ "dependencies": {
++ "sprintf-js": {
++ "version": "1.1.2",
++ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
++ "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
++ "dev": true,
++ "optional": true
++ }
++ }
++ },
+ "rxjs": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz",
+@@ -2666,6 +2750,13 @@
+ "integrity": "sha1-eQp89v6lRZuslhELKbYEEtyP+Ws=",
+ "dev": true
+ },
++ "semver-compare": {
++ "version": "1.0.0",
++ "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
++ "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
++ "dev": true,
++ "optional": true
++ },
+ "semver-diff": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz",
+@@ -2675,6 +2766,16 @@
+ "semver": "^5.0.3"
+ }
+ },
++ "serialize-error": {
++ "version": "7.0.1",
++ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
++ "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
++ "dev": true,
++ "optional": true,
++ "requires": {
++ "type-fest": "^0.13.1"
++ }
++ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+@@ -2702,15 +2803,6 @@
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+ "dev": true
+ },
+- "single-line-log": {
+- "version": "1.1.2",
+- "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz",
+- "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=",
+- "dev": true,
+- "requires": {
+- "string-width": "^1.0.1"
+- }
+- },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+@@ -2787,12 +2879,6 @@
+ "integrity": "sha1-NpS1gEVnpFjTyARYQqY1hjL2JlQ=",
+ "dev": true
+ },
+- "speedometer": {
+- "version": "0.1.4",
+- "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz",
+- "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=",
+- "dev": true
+- },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+@@ -2834,10 +2920,21 @@
+ }
+ },
+ "string_decoder": {
+- "version": "0.10.31",
+- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+- "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+- "dev": true
++ "version": "1.1.1",
++ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
++ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
++ "dev": true,
++ "requires": {
++ "safe-buffer": "~5.1.0"
++ },
++ "dependencies": {
++ "safe-buffer": {
++ "version": "5.1.2",
++ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
++ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
++ "dev": true
++ }
++ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+@@ -2848,30 +2945,12 @@
+ "ansi-regex": "^2.0.0"
+ }
+ },
+- "strip-bom": {
+- "version": "2.0.0",
+- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+- "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+- "dev": true,
+- "requires": {
+- "is-utf8": "^0.2.0"
+- }
+- },
+ "strip-eof": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+ "dev": true
+ },
+- "strip-indent": {
+- "version": "1.0.1",
+- "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+- "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+- "dev": true,
+- "requires": {
+- "get-stdin": "^4.0.1"
+- }
+- },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+@@ -2879,12 +2958,23 @@
+ "dev": true
+ },
+ "sumchecker": {
+- "version": "2.0.2",
+- "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz",
+- "integrity": "sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=",
++ "version": "3.0.1",
++ "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
++ "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
+ "dev": true,
+ "requires": {
+- "debug": "^2.2.0"
++ "debug": "^4.1.0"
++ },
++ "dependencies": {
++ "debug": {
++ "version": "4.2.0",
++ "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
++ "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
++ "dev": true,
++ "requires": {
++ "ms": "2.1.2"
++ }
++ }
+ }
+ },
+ "supports-color": {
+@@ -2943,28 +3033,18 @@
+ "execa": "^0.7.0"
+ }
+ },
+- "throttleit": {
+- "version": "0.0.2",
+- "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz",
+- "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=",
+- "dev": true
+- },
+- "through2": {
+- "version": "0.2.3",
+- "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz",
+- "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=",
+- "dev": true,
+- "requires": {
+- "readable-stream": "~1.1.9",
+- "xtend": "~2.1.1"
+- }
+- },
+ "timed-out": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
+ "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=",
+ "dev": true
+ },
++ "to-readable-stream": {
++ "version": "1.0.0",
++ "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz",
++ "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==",
++ "dev": true
++ },
+ "tough-cookie": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
+@@ -2983,12 +3063,6 @@
+ }
+ }
+ },
+- "trim-newlines": {
+- "version": "1.0.0",
+- "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+- "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
+- "dev": true
+- },
+ "truncate-utf8-bytes": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
+@@ -3004,6 +3078,13 @@
+ "integrity": "sha1-w8GflZc/sKYpc/sJ2Q2WHuQ+XIo=",
+ "dev": true
+ },
++ "tunnel": {
++ "version": "0.0.6",
++ "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
++ "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
++ "dev": true,
++ "optional": true
++ },
+ "tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+@@ -3019,6 +3100,13 @@
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+ "dev": true
+ },
++ "type-fest": {
++ "version": "0.13.1",
++ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
++ "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
++ "dev": true,
++ "optional": true
++ },
+ "typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+@@ -3276,15 +3364,6 @@
+ "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=",
+ "dev": true
+ },
+- "xtend": {
+- "version": "2.1.2",
+- "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz",
+- "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=",
+- "dev": true,
+- "requires": {
+- "object-keys": "~0.4.0"
+- }
+- },
+ "y18n": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+@@ -3377,12 +3456,13 @@
+ }
+ },
+ "yauzl": {
+- "version": "2.4.1",
+- "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
+- "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
++ "version": "2.10.0",
++ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
++ "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
+ "dev": true,
+ "requires": {
+- "fd-slicer": "~1.0.1"
++ "buffer-crc32": "~0.2.3",
++ "fd-slicer": "~1.1.0"
+ }
+ }
+ }
+diff --git a/package.json b/package.json
+index db31c3b..9c2d20c 100644
+--- a/package.json
++++ b/package.json
+@@ -1,7 +1,7 @@
+ {
+ "name": "Pencil",
+ "devDependencies": {
+- "electron": "6.0.1",
++ "electron": "10.1.5",
+ "electron-builder": "20.28.4",
+ "electron-rebuild": "^1.8.5",
+ "rimraf": "^2.5.4"
+@@ -71,7 +71,7 @@
+ "postinstall": "install-app-deps",
+ "install-app-deps": "node ./node_modules/electron-builder/out/install-app-deps.js",
+ "start": "./node_modules/.bin/electron ./app",
+- "start:dev": "./node_modules/.bin/electron ./app --enable-dev --enable-transparent-visuals --disable-gpu",
++ "start:dev": "./node_modules/.bin/electron ./app --enable-dev --enable-transparent-visuals",
+ "start:mac": "./node_modules/.bin/electron ./app --enable-dev",
+ "clean": "rimraf dist",
+ "pack": "build",