├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── build.js ├── css └── impressionist.css ├── favicon.png ├── js └── impressionist.js ├── main.js ├── package-lock.json ├── package.json └── src ├── impressionist.css ├── impressionist.js ├── lib ├── css3.js ├── gc.js └── util.js ├── main ├── README.md ├── fileOpenSave.js └── menu.js └── plugins ├── axis ├── axis.css └── axis.js ├── camera ├── camera.css └── camera.js ├── cameracontrols ├── cameracontrols.css └── cameracontrols.js ├── electron └── electron.js ├── loadcss └── loadcss.js ├── stepedit ├── stepedit.css └── stepedit.js ├── stepstyles ├── stepstyles.css └── stepstyles.js ├── tinymce ├── tinymce.css └── tinymce.js └── toolbar ├── toolbar.css └── toolbar.js /.gitignore: -------------------------------------------------------------------------------- 1 | /js/impressionist.min.js 2 | /node_modules 3 | /npm-debug.log 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "templates"] 2 | path = templates 3 | url = https://github.com/henrikingo/impressionist-templates.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Henrik Ingo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Impressionist 2 | 3 | Impressionist is a prototype and concept to build a visual (wysiwyg) editor for creating 4 | [impress.js presentations](http://henrikingo.github.io/impress.js/examples/classic-slides/). 5 | The idea is to use [Electron](http://electron.atom.io/) to make the browser based 6 | impress.js presentation into a proper desktop app that can open and save files. 7 | [TinyMCE](https://www.tinymce.com/docs/demo/inline/) is integrated to provide the editing 8 | capability. 9 | 10 | Current status is that you can open a presentations (such as the ones under 11 | [templates/](https://github.com/henrikingo/impressionist-templates)) then move, add or remove slides and edit their contents with TinyMCE. You 12 | can't really modify the style of slides, or have different kinds of slides. For that you'd still 13 | have to edit raw CSS and HTML. 14 | 15 | ## Demo 16 | 17 | * [Short](https://www.youtube.com/watch?v=OHG27IBeuHM) 18 | * [17 minutes, narrated](https://www.youtube.com/watch?v=c07w0hsC4yQ) 19 | 20 | ## HOWTO 21 | 22 | Pre-requisites: git, node 6.5+ and npm 5+ 23 | 24 | git clone --recursive https://github.com/henrikingo/impressionist.git 25 | cd impressionist 26 | npm install 27 | npm start 28 | 29 | 30 | ## Mailing list 31 | 32 | [impressionist-presentations](https://groups.google.com/forum/#!forum/impressionist-presentations) 33 | 34 | ## Repository organization 35 | 36 | ### Electron main process 37 | 38 | * `main.js` 39 | * `src/main/*` 40 | 41 | ### Browser process / Electron renderer process 42 | 43 | * `src/impressionist.*` 44 | * `src/lib/*.js` 45 | * `src/plugins/*` 46 | 47 | ...where `lib` functions are common utility functions and called synchronously. `plugins` are 48 | features, implemented as anonymous closures, and use the event based communication 49 | mechanism familiar from impress.js. 50 | 51 | Use `npm build` or just `node build.js` to build the above into `js/impressionist.js` and 52 | `css/impressionist.css`. 53 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | var buildify = require('buildify'); 2 | 3 | 4 | buildify() 5 | // Core impressionist file that provides impressionist() api function. 6 | .load('src/impressionist.js') 7 | // Libraries that are made available early, used by other plugins 8 | .concat(['src/lib/css3.js', 9 | 'src/lib/gc.js', 10 | 'src/lib/util.js']) 11 | // Plugins provide features 12 | .concat([ 13 | // Disabled when we moved to electron: 'src/plugins/axis/axis.js', 14 | 'src/plugins/camera/camera.js', 15 | 'src/plugins/cameracontrols/cameracontrols.js', 16 | 'src/plugins/electron/electron.js', 17 | 'src/plugins/loadcss/loadcss.js', 18 | 'src/plugins/stepedit/stepedit.js', 19 | 'src/plugins/stepstyles/stepstyles.js', 20 | 'src/plugins/tinymce/tinymce.js', 21 | 'src/plugins/toolbar/toolbar.js']) 22 | .save('js/impressionist.js') 23 | // .uglify() 24 | // .save('js/impressionist.min.js'); 25 | 26 | 27 | buildify() 28 | .load('src/impressionist.css') 29 | .concat([ 30 | // Disabled when we moved to electron: 'src/plugins/axis/axis.css', 31 | 'src/plugins/camera/camera.css', 32 | 'src/plugins/cameracontrols/cameracontrols.css', 33 | 'src/plugins/stepedit/stepedit.css', 34 | 'src/plugins/stepstyles/stepstyles.css', 35 | 'src/plugins/tinymce/tinymce.css', 36 | 'src/plugins/toolbar/toolbar.css']) 37 | .save('css/impressionist.css'); 38 | -------------------------------------------------------------------------------- /css/impressionist.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS styles for Impressionist presentations editor 3 | * 4 | * Copyright 2016, Henrik Ingo (@henrikingo) 5 | * MIT License 6 | */ 7 | 8 | /* Camera plugin */ 9 | 10 | #impressionist-toolbar .impressionist-camera-minus, 11 | #impressionist-toolbar .impressionist-camera-plus, 12 | #impressionist-toolbar .impressionist-step-minus, 13 | #impressionist-toolbar .impressionist-step-plus { 14 | font-size: 70%; 15 | width: 25px; 16 | height: 15px; 17 | padding: 0; 18 | border: 0; 19 | background: none; 20 | 21 | position: relative; 22 | top: 15px; 23 | left: -47px; 24 | } 25 | 26 | /* Cut away the empty space left by the plus and minus buttons as they were relatively positioned elsewhere. */ 27 | #impressionist-toolbar-group-1 span, 28 | #impressionist-toolbar-group-2 span { 29 | margin-right: -48px; 30 | } 31 | #impressionist-toolbar-group-1 span.nocut, 32 | #impressionist-toolbar-group-2 span.nocut { 33 | margin-right: none; 34 | } 35 | 36 | /* Cameracontrols plugin (the buttons that allow you to pan, zoom, scale and rotate the camera) */ 37 | 38 | #impressionist-cameracontrols { 39 | pointer-events: auto; 40 | } 41 | 42 | #impressionist-cameracontrols-xy { 43 | position: fixed; 44 | bottom: 50px; 45 | left: 50px; 46 | } 47 | #impressionist-cameracontrols-z { 48 | position: fixed; 49 | top: 50px; 50 | right: 50px; 51 | } 52 | #impressionist-cameracontrols-rotate { 53 | position: fixed; 54 | bottom: 50px; 55 | right: 50px; 56 | } 57 | 58 | /* Stepedit plugin. Reorder steps dialog. */ 59 | 60 | #impressionist-stepedit-reorder-dialog { 61 | pointer-events: auto; 62 | background-color: #fff; 63 | border-bottom: solid 1px #ddd; 64 | border-right: solid 1px #ddd; 65 | opacity: 0.9; 66 | color: #000; 67 | padding: 10px; 68 | width: 200px; 69 | } 70 | 71 | #impressionist-stepedit-reorder-dialog table { 72 | width: 100%; 73 | } 74 | 75 | #impressionist-stepedit-reorder-dialog td { 76 | vertical-align: middle; 77 | } 78 | 79 | #impressionist-stepedit-reorder-dialog button { 80 | width: 22px; 81 | height: 22px; 82 | } 83 | #impressionist-toolbar-groups input#impressionist-stepstyles-id, 84 | #impressionist-toolbar-groups input#impressionist-stepstyles-class { 85 | width: 10em; 86 | } 87 | 88 | #impressionist-stepstyles-class-dialog { 89 | pointer-events: auto; 90 | 91 | position: relative; 92 | left: 300px; 93 | 94 | background-color: #fff; 95 | border-bottom: solid 1px #ddd; 96 | border-right: solid 1px #ddd; 97 | opacity: 0.9; 98 | color: #000; 99 | padding: 10px; 100 | width: 200px; 101 | } 102 | 103 | #impressionist-stepstyles-class-select { 104 | width: 100%; 105 | } 106 | /* Tinymce integration */ 107 | #tinymce-toolbar, 108 | .mce-floatpanel { 109 | pointer-events: auto; 110 | } 111 | /** 112 | * CSS styles for Impressionist presentations editor 113 | * 114 | * Copyright 2016, Henrik Ingo (@henrikingo) 115 | * MIT License 116 | */ 117 | 118 | /* Many impress.js presentations set pointer-events: none. We must turn them back on for our toolbar. */ 119 | #impressionist-toolbar { 120 | pointer-events: auto; 121 | opacity: 0.9; 122 | color: #000; 123 | } 124 | 125 | #impressionist-toolbar-groups { 126 | background-color: #fff; 127 | padding-bottom: 15px; 128 | padding-top: 8px; 129 | padding-left: 3px; 130 | padding-right: 3px; 131 | border-bottom: solid 1px #ddd; 132 | border-right: solid 1px #ddd; 133 | } 134 | 135 | #impressionist-toolbar-groups input { 136 | width: 3em; 137 | font-size: 80%; 138 | opacity: 1; 139 | } 140 | #impressionist-toolbar-groups input[type="checkbox"] { 141 | width: 1em; 142 | } 143 | 144 | #impressionist-toolbar-titles button { 145 | font-size: 70%; 146 | } 147 | 148 | #impressionist-toolbar-titles button.selected { 149 | font-weight: bold; 150 | } -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henrikingo/impressionist/788e6d05e50287ea52911ae485a27054a46c3516/favicon.png -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron') 2 | const app = electron.app 3 | const BrowserWindow = electron.BrowserWindow 4 | const menu = require('./src/main/menu') 5 | 6 | // Keep a global reference of the window object, if you don't, the window will 7 | // be closed automatically when the JavaScript object is garbage collected. 8 | let mainWindow 9 | 10 | function createWindow () { 11 | // Create the browser window. 12 | mainWindow = new BrowserWindow({useContentSize: true, 13 | icon: './favicon.png'}) 14 | mainWindow.maximize() 15 | // debug 16 | // mainWindow.webContents.openDevTools() 17 | 18 | // This triggers when user opens a presentation and it has loaded into the window 19 | mainWindow.webContents.on('dom-ready', function(event) { 20 | mainWindow.webContents.executeJavaScript(loadImpressionist()) 21 | }) 22 | 23 | mainWindow.on('closed', function () { 24 | mainWindow = null 25 | }) 26 | menu.createMenu(mainWindow) 27 | } 28 | 29 | app.on('ready', createWindow) 30 | app.on('window-all-closed', function () { 31 | // On OS X it is common for applications and their menu bar 32 | // to stay active until the user quits explicitly with Cmd + Q 33 | if (process.platform !== 'darwin') { 34 | app.quit() 35 | } 36 | }) 37 | app.on('activate', function () { 38 | // On OS X it's common to re-create a window in the app when the 39 | // dock icon is clicked and there are no other windows open. 40 | if (mainWindow === null) { 41 | createWindow() 42 | } 43 | }) 44 | 45 | 46 | function loadImpressionist () { 47 | var impressionistRoot = __dirname; 48 | // Windows support 49 | impressionistRoot = impressionistRoot.split("\\").join("/"); 50 | 51 | return `var script = document.createElement("script"); 52 | script.src = "${impressionistRoot}/js/impressionist.js"; 53 | script.type = "text/javascript"; 54 | script.onload = function(){ 55 | impressionist().rootDir = '${impressionistRoot}'; 56 | impressionist().gc.pushElement(script); 57 | impressionist().util.triggerEvent(document, "impressionist:init", {}); 58 | }; 59 | document.head.appendChild(script); 60 | ` 61 | } 62 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "impressionist", 3 | "version": "0.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ajv": { 8 | "version": "6.12.2", 9 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", 10 | "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", 11 | "dev": true, 12 | "requires": { 13 | "fast-deep-equal": "^3.1.1", 14 | "fast-json-stable-stringify": "^2.0.0", 15 | "json-schema-traverse": "^0.4.1", 16 | "uri-js": "^4.2.2" 17 | } 18 | }, 19 | "ansi-regex": { 20 | "version": "2.1.1", 21 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 22 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 23 | "dev": true 24 | }, 25 | "array-find-index": { 26 | "version": "1.0.2", 27 | "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", 28 | "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", 29 | "dev": true 30 | }, 31 | "asn1": { 32 | "version": "0.2.4", 33 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 34 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 35 | "dev": true, 36 | "requires": { 37 | "safer-buffer": "~2.1.0" 38 | } 39 | }, 40 | "assert-plus": { 41 | "version": "1.0.0", 42 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 43 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", 44 | "dev": true 45 | }, 46 | "asynckit": { 47 | "version": "0.4.0", 48 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 49 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", 50 | "dev": true 51 | }, 52 | "aws-sign2": { 53 | "version": "0.7.0", 54 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 55 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", 56 | "dev": true 57 | }, 58 | "aws4": { 59 | "version": "1.9.1", 60 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", 61 | "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", 62 | "dev": true 63 | }, 64 | "balanced-match": { 65 | "version": "1.0.0", 66 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 67 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 68 | "dev": true 69 | }, 70 | "bcrypt-pbkdf": { 71 | "version": "1.0.2", 72 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 73 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 74 | "dev": true, 75 | "requires": { 76 | "tweetnacl": "^0.14.3" 77 | } 78 | }, 79 | "brace-expansion": { 80 | "version": "1.1.11", 81 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 82 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 83 | "dev": true, 84 | "requires": { 85 | "balanced-match": "^1.0.0", 86 | "concat-map": "0.0.1" 87 | } 88 | }, 89 | "buffer-crc32": { 90 | "version": "0.2.13", 91 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", 92 | "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", 93 | "dev": true 94 | }, 95 | "buffer-from": { 96 | "version": "1.1.1", 97 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 98 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 99 | "dev": true 100 | }, 101 | "buildify": { 102 | "version": "0.4.0", 103 | "resolved": "https://registry.npmjs.org/buildify/-/buildify-0.4.0.tgz", 104 | "integrity": "sha1-IMi94gSIsyWIzpKkGJZyoITToMQ=", 105 | "dev": true, 106 | "requires": { 107 | "clean-css": "0.6.0", 108 | "mkdirp": "0.3.2", 109 | "uglify-js": "1.3.4", 110 | "underscore": "1.3.3" 111 | } 112 | }, 113 | "camelcase": { 114 | "version": "2.1.1", 115 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", 116 | "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", 117 | "dev": true 118 | }, 119 | "camelcase-keys": { 120 | "version": "2.1.0", 121 | "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", 122 | "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", 123 | "dev": true, 124 | "requires": { 125 | "camelcase": "^2.0.0", 126 | "map-obj": "^1.0.0" 127 | } 128 | }, 129 | "caseless": { 130 | "version": "0.12.0", 131 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 132 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", 133 | "dev": true 134 | }, 135 | "clean-css": { 136 | "version": "0.6.0", 137 | "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-0.6.0.tgz", 138 | "integrity": "sha1-DKQi/ArTQoVsa4RCGU1UbiZEac4=", 139 | "dev": true, 140 | "requires": { 141 | "optimist": "0.3.x" 142 | } 143 | }, 144 | "code-point-at": { 145 | "version": "1.1.0", 146 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 147 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", 148 | "dev": true 149 | }, 150 | "combined-stream": { 151 | "version": "1.0.8", 152 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 153 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 154 | "dev": true, 155 | "requires": { 156 | "delayed-stream": "~1.0.0" 157 | } 158 | }, 159 | "concat-map": { 160 | "version": "0.0.1", 161 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 162 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 163 | "dev": true 164 | }, 165 | "concat-stream": { 166 | "version": "1.6.2", 167 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 168 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 169 | "dev": true, 170 | "requires": { 171 | "buffer-from": "^1.0.0", 172 | "inherits": "^2.0.3", 173 | "readable-stream": "^2.2.2", 174 | "typedarray": "^0.0.6" 175 | }, 176 | "dependencies": { 177 | "isarray": { 178 | "version": "1.0.0", 179 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 180 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 181 | "dev": true 182 | }, 183 | "readable-stream": { 184 | "version": "2.3.7", 185 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 186 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 187 | "dev": true, 188 | "requires": { 189 | "core-util-is": "~1.0.0", 190 | "inherits": "~2.0.3", 191 | "isarray": "~1.0.0", 192 | "process-nextick-args": "~2.0.0", 193 | "safe-buffer": "~5.1.1", 194 | "string_decoder": "~1.1.1", 195 | "util-deprecate": "~1.0.1" 196 | } 197 | }, 198 | "safe-buffer": { 199 | "version": "5.1.2", 200 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 201 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 202 | "dev": true 203 | }, 204 | "string_decoder": { 205 | "version": "1.1.1", 206 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 207 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 208 | "dev": true, 209 | "requires": { 210 | "safe-buffer": "~5.1.0" 211 | } 212 | } 213 | } 214 | }, 215 | "core-util-is": { 216 | "version": "1.0.2", 217 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 218 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 219 | "dev": true 220 | }, 221 | "currently-unhandled": { 222 | "version": "0.4.1", 223 | "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", 224 | "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", 225 | "dev": true, 226 | "requires": { 227 | "array-find-index": "^1.0.1" 228 | } 229 | }, 230 | "dashdash": { 231 | "version": "1.14.1", 232 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 233 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 234 | "dev": true, 235 | "requires": { 236 | "assert-plus": "^1.0.0" 237 | } 238 | }, 239 | "debug": { 240 | "version": "2.6.9", 241 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 242 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 243 | "dev": true, 244 | "requires": { 245 | "ms": "2.0.0" 246 | } 247 | }, 248 | "decamelize": { 249 | "version": "1.2.0", 250 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 251 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 252 | "dev": true 253 | }, 254 | "deep-extend": { 255 | "version": "0.6.0", 256 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 257 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", 258 | "dev": true 259 | }, 260 | "delayed-stream": { 261 | "version": "1.0.0", 262 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 263 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", 264 | "dev": true 265 | }, 266 | "ecc-jsbn": { 267 | "version": "0.1.2", 268 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 269 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 270 | "dev": true, 271 | "requires": { 272 | "jsbn": "~0.1.0", 273 | "safer-buffer": "^2.1.0" 274 | } 275 | }, 276 | "electron": { 277 | "version": "1.4.4", 278 | "resolved": "https://registry.npmjs.org/electron/-/electron-1.4.4.tgz", 279 | "integrity": "sha1-hgALzhXgbK3DVmy3yGEiWQ3uVKU=", 280 | "dev": true, 281 | "requires": { 282 | "electron-download": "^3.0.1", 283 | "extract-zip": "^1.0.3" 284 | } 285 | }, 286 | "electron-download": { 287 | "version": "3.3.0", 288 | "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-3.3.0.tgz", 289 | "integrity": "sha1-LP1U1pZsAZxNSa1l++Zcyc3vaMg=", 290 | "dev": true, 291 | "requires": { 292 | "debug": "^2.2.0", 293 | "fs-extra": "^0.30.0", 294 | "home-path": "^1.0.1", 295 | "minimist": "^1.2.0", 296 | "nugget": "^2.0.0", 297 | "path-exists": "^2.1.0", 298 | "rc": "^1.1.2", 299 | "semver": "^5.3.0", 300 | "sumchecker": "^1.2.0" 301 | } 302 | }, 303 | "error-ex": { 304 | "version": "1.3.2", 305 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 306 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 307 | "dev": true, 308 | "requires": { 309 | "is-arrayish": "^0.2.1" 310 | } 311 | }, 312 | "es6-promise": { 313 | "version": "4.2.8", 314 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", 315 | "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", 316 | "dev": true 317 | }, 318 | "extend": { 319 | "version": "3.0.2", 320 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 321 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", 322 | "dev": true 323 | }, 324 | "extract-zip": { 325 | "version": "1.7.0", 326 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", 327 | "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", 328 | "dev": true, 329 | "requires": { 330 | "concat-stream": "^1.6.2", 331 | "debug": "^2.6.9", 332 | "mkdirp": "^0.5.4", 333 | "yauzl": "^2.10.0" 334 | }, 335 | "dependencies": { 336 | "mkdirp": { 337 | "version": "0.5.5", 338 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 339 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 340 | "dev": true, 341 | "requires": { 342 | "minimist": "^1.2.5" 343 | } 344 | } 345 | } 346 | }, 347 | "extsprintf": { 348 | "version": "1.3.0", 349 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 350 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", 351 | "dev": true 352 | }, 353 | "fast-deep-equal": { 354 | "version": "3.1.1", 355 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", 356 | "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", 357 | "dev": true 358 | }, 359 | "fast-json-stable-stringify": { 360 | "version": "2.1.0", 361 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 362 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 363 | "dev": true 364 | }, 365 | "fd-slicer": { 366 | "version": "1.1.0", 367 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", 368 | "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", 369 | "dev": true, 370 | "requires": { 371 | "pend": "~1.2.0" 372 | } 373 | }, 374 | "find-up": { 375 | "version": "1.1.2", 376 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", 377 | "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", 378 | "dev": true, 379 | "requires": { 380 | "path-exists": "^2.0.0", 381 | "pinkie-promise": "^2.0.0" 382 | } 383 | }, 384 | "forever-agent": { 385 | "version": "0.6.1", 386 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 387 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", 388 | "dev": true 389 | }, 390 | "form-data": { 391 | "version": "2.3.3", 392 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 393 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 394 | "dev": true, 395 | "requires": { 396 | "asynckit": "^0.4.0", 397 | "combined-stream": "^1.0.6", 398 | "mime-types": "^2.1.12" 399 | } 400 | }, 401 | "fs-extra": { 402 | "version": "0.30.0", 403 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", 404 | "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", 405 | "dev": true, 406 | "requires": { 407 | "graceful-fs": "^4.1.2", 408 | "jsonfile": "^2.1.0", 409 | "klaw": "^1.0.0", 410 | "path-is-absolute": "^1.0.0", 411 | "rimraf": "^2.2.8" 412 | } 413 | }, 414 | "fs.realpath": { 415 | "version": "1.0.0", 416 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 417 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 418 | "dev": true 419 | }, 420 | "get-stdin": { 421 | "version": "4.0.1", 422 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", 423 | "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", 424 | "dev": true 425 | }, 426 | "getpass": { 427 | "version": "0.1.7", 428 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 429 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 430 | "dev": true, 431 | "requires": { 432 | "assert-plus": "^1.0.0" 433 | } 434 | }, 435 | "glob": { 436 | "version": "7.1.6", 437 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 438 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 439 | "dev": true, 440 | "requires": { 441 | "fs.realpath": "^1.0.0", 442 | "inflight": "^1.0.4", 443 | "inherits": "2", 444 | "minimatch": "^3.0.4", 445 | "once": "^1.3.0", 446 | "path-is-absolute": "^1.0.0" 447 | } 448 | }, 449 | "graceful-fs": { 450 | "version": "4.2.4", 451 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", 452 | "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", 453 | "dev": true 454 | }, 455 | "har-schema": { 456 | "version": "2.0.0", 457 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 458 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", 459 | "dev": true 460 | }, 461 | "har-validator": { 462 | "version": "5.1.3", 463 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 464 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 465 | "dev": true, 466 | "requires": { 467 | "ajv": "^6.5.5", 468 | "har-schema": "^2.0.0" 469 | } 470 | }, 471 | "home-path": { 472 | "version": "1.0.7", 473 | "resolved": "https://registry.npmjs.org/home-path/-/home-path-1.0.7.tgz", 474 | "integrity": "sha512-tM1pVa+u3ZqQwIkXcWfhUlY3HWS3TsnKsfi2OHHvnhkX52s9etyktPyy1rQotkr0euWimChDq+QkQuDe8ngUlQ==", 475 | "dev": true 476 | }, 477 | "hosted-git-info": { 478 | "version": "2.8.8", 479 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", 480 | "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", 481 | "dev": true 482 | }, 483 | "http-signature": { 484 | "version": "1.2.0", 485 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 486 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 487 | "dev": true, 488 | "requires": { 489 | "assert-plus": "^1.0.0", 490 | "jsprim": "^1.2.2", 491 | "sshpk": "^1.7.0" 492 | } 493 | }, 494 | "indent-string": { 495 | "version": "2.1.0", 496 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", 497 | "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", 498 | "dev": true, 499 | "requires": { 500 | "repeating": "^2.0.0" 501 | } 502 | }, 503 | "inflight": { 504 | "version": "1.0.6", 505 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 506 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 507 | "dev": true, 508 | "requires": { 509 | "once": "^1.3.0", 510 | "wrappy": "1" 511 | } 512 | }, 513 | "inherits": { 514 | "version": "2.0.4", 515 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 516 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 517 | "dev": true 518 | }, 519 | "ini": { 520 | "version": "1.3.5", 521 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", 522 | "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", 523 | "dev": true 524 | }, 525 | "is-arrayish": { 526 | "version": "0.2.1", 527 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 528 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 529 | "dev": true 530 | }, 531 | "is-finite": { 532 | "version": "1.1.0", 533 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", 534 | "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", 535 | "dev": true 536 | }, 537 | "is-fullwidth-code-point": { 538 | "version": "1.0.0", 539 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 540 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 541 | "dev": true, 542 | "requires": { 543 | "number-is-nan": "^1.0.0" 544 | } 545 | }, 546 | "is-typedarray": { 547 | "version": "1.0.0", 548 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 549 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", 550 | "dev": true 551 | }, 552 | "is-utf8": { 553 | "version": "0.2.1", 554 | "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", 555 | "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", 556 | "dev": true 557 | }, 558 | "isarray": { 559 | "version": "0.0.1", 560 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 561 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 562 | "dev": true 563 | }, 564 | "isstream": { 565 | "version": "0.1.2", 566 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 567 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", 568 | "dev": true 569 | }, 570 | "jsbn": { 571 | "version": "0.1.1", 572 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 573 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 574 | "dev": true 575 | }, 576 | "json-schema": { 577 | "version": "0.2.3", 578 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 579 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", 580 | "dev": true 581 | }, 582 | "json-schema-traverse": { 583 | "version": "0.4.1", 584 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 585 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 586 | "dev": true 587 | }, 588 | "json-stringify-safe": { 589 | "version": "5.0.1", 590 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 591 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", 592 | "dev": true 593 | }, 594 | "jsonfile": { 595 | "version": "2.4.0", 596 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", 597 | "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", 598 | "dev": true, 599 | "requires": { 600 | "graceful-fs": "^4.1.6" 601 | } 602 | }, 603 | "jsprim": { 604 | "version": "1.4.1", 605 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 606 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 607 | "dev": true, 608 | "requires": { 609 | "assert-plus": "1.0.0", 610 | "extsprintf": "1.3.0", 611 | "json-schema": "0.2.3", 612 | "verror": "1.10.0" 613 | } 614 | }, 615 | "klaw": { 616 | "version": "1.3.1", 617 | "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", 618 | "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", 619 | "dev": true, 620 | "requires": { 621 | "graceful-fs": "^4.1.9" 622 | } 623 | }, 624 | "load-json-file": { 625 | "version": "1.1.0", 626 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", 627 | "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", 628 | "dev": true, 629 | "requires": { 630 | "graceful-fs": "^4.1.2", 631 | "parse-json": "^2.2.0", 632 | "pify": "^2.0.0", 633 | "pinkie-promise": "^2.0.0", 634 | "strip-bom": "^2.0.0" 635 | } 636 | }, 637 | "loud-rejection": { 638 | "version": "1.6.0", 639 | "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", 640 | "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", 641 | "dev": true, 642 | "requires": { 643 | "currently-unhandled": "^0.4.1", 644 | "signal-exit": "^3.0.0" 645 | } 646 | }, 647 | "map-obj": { 648 | "version": "1.0.1", 649 | "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", 650 | "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", 651 | "dev": true 652 | }, 653 | "meow": { 654 | "version": "3.7.0", 655 | "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", 656 | "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", 657 | "dev": true, 658 | "requires": { 659 | "camelcase-keys": "^2.0.0", 660 | "decamelize": "^1.1.2", 661 | "loud-rejection": "^1.0.0", 662 | "map-obj": "^1.0.1", 663 | "minimist": "^1.1.3", 664 | "normalize-package-data": "^2.3.4", 665 | "object-assign": "^4.0.1", 666 | "read-pkg-up": "^1.0.1", 667 | "redent": "^1.0.0", 668 | "trim-newlines": "^1.0.0" 669 | } 670 | }, 671 | "mime-db": { 672 | "version": "1.44.0", 673 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 674 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", 675 | "dev": true 676 | }, 677 | "mime-types": { 678 | "version": "2.1.27", 679 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 680 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 681 | "dev": true, 682 | "requires": { 683 | "mime-db": "1.44.0" 684 | } 685 | }, 686 | "minimatch": { 687 | "version": "3.0.4", 688 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 689 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 690 | "dev": true, 691 | "requires": { 692 | "brace-expansion": "^1.1.7" 693 | } 694 | }, 695 | "minimist": { 696 | "version": "1.2.5", 697 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 698 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 699 | "dev": true 700 | }, 701 | "mkdirp": { 702 | "version": "0.3.2", 703 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.2.tgz", 704 | "integrity": "sha1-S/uJHpxIuT1rVn8sPPLdP1a83vg=", 705 | "dev": true 706 | }, 707 | "ms": { 708 | "version": "2.0.0", 709 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 710 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 711 | "dev": true 712 | }, 713 | "normalize-package-data": { 714 | "version": "2.5.0", 715 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 716 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 717 | "dev": true, 718 | "requires": { 719 | "hosted-git-info": "^2.1.4", 720 | "resolve": "^1.10.0", 721 | "semver": "2 || 3 || 4 || 5", 722 | "validate-npm-package-license": "^3.0.1" 723 | } 724 | }, 725 | "nugget": { 726 | "version": "2.0.1", 727 | "resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz", 728 | "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=", 729 | "dev": true, 730 | "requires": { 731 | "debug": "^2.1.3", 732 | "minimist": "^1.1.0", 733 | "pretty-bytes": "^1.0.2", 734 | "progress-stream": "^1.1.0", 735 | "request": "^2.45.0", 736 | "single-line-log": "^1.1.2", 737 | "throttleit": "0.0.2" 738 | } 739 | }, 740 | "number-is-nan": { 741 | "version": "1.0.1", 742 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 743 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", 744 | "dev": true 745 | }, 746 | "oauth-sign": { 747 | "version": "0.9.0", 748 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 749 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", 750 | "dev": true 751 | }, 752 | "object-assign": { 753 | "version": "4.1.1", 754 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 755 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 756 | "dev": true 757 | }, 758 | "object-keys": { 759 | "version": "0.4.0", 760 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", 761 | "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", 762 | "dev": true 763 | }, 764 | "once": { 765 | "version": "1.4.0", 766 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 767 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 768 | "dev": true, 769 | "requires": { 770 | "wrappy": "1" 771 | } 772 | }, 773 | "optimist": { 774 | "version": "0.3.7", 775 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", 776 | "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", 777 | "dev": true, 778 | "requires": { 779 | "wordwrap": "~0.0.2" 780 | } 781 | }, 782 | "parse-json": { 783 | "version": "2.2.0", 784 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", 785 | "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", 786 | "dev": true, 787 | "requires": { 788 | "error-ex": "^1.2.0" 789 | } 790 | }, 791 | "path-exists": { 792 | "version": "2.1.0", 793 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", 794 | "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", 795 | "dev": true, 796 | "requires": { 797 | "pinkie-promise": "^2.0.0" 798 | } 799 | }, 800 | "path-is-absolute": { 801 | "version": "1.0.1", 802 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 803 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 804 | "dev": true 805 | }, 806 | "path-parse": { 807 | "version": "1.0.6", 808 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 809 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 810 | "dev": true 811 | }, 812 | "path-type": { 813 | "version": "1.1.0", 814 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", 815 | "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", 816 | "dev": true, 817 | "requires": { 818 | "graceful-fs": "^4.1.2", 819 | "pify": "^2.0.0", 820 | "pinkie-promise": "^2.0.0" 821 | } 822 | }, 823 | "pend": { 824 | "version": "1.2.0", 825 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 826 | "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", 827 | "dev": true 828 | }, 829 | "performance-now": { 830 | "version": "2.1.0", 831 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 832 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", 833 | "dev": true 834 | }, 835 | "pify": { 836 | "version": "2.3.0", 837 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 838 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", 839 | "dev": true 840 | }, 841 | "pinkie": { 842 | "version": "2.0.4", 843 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 844 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", 845 | "dev": true 846 | }, 847 | "pinkie-promise": { 848 | "version": "2.0.1", 849 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 850 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 851 | "dev": true, 852 | "requires": { 853 | "pinkie": "^2.0.0" 854 | } 855 | }, 856 | "pretty-bytes": { 857 | "version": "1.0.4", 858 | "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", 859 | "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", 860 | "dev": true, 861 | "requires": { 862 | "get-stdin": "^4.0.1", 863 | "meow": "^3.1.0" 864 | } 865 | }, 866 | "process-nextick-args": { 867 | "version": "2.0.1", 868 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 869 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 870 | "dev": true 871 | }, 872 | "progress-stream": { 873 | "version": "1.2.0", 874 | "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz", 875 | "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c=", 876 | "dev": true, 877 | "requires": { 878 | "speedometer": "~0.1.2", 879 | "through2": "~0.2.3" 880 | } 881 | }, 882 | "psl": { 883 | "version": "1.8.0", 884 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", 885 | "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", 886 | "dev": true 887 | }, 888 | "punycode": { 889 | "version": "2.1.1", 890 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 891 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 892 | "dev": true 893 | }, 894 | "qs": { 895 | "version": "6.5.2", 896 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 897 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", 898 | "dev": true 899 | }, 900 | "rc": { 901 | "version": "1.2.8", 902 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 903 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 904 | "dev": true, 905 | "requires": { 906 | "deep-extend": "^0.6.0", 907 | "ini": "~1.3.0", 908 | "minimist": "^1.2.0", 909 | "strip-json-comments": "~2.0.1" 910 | } 911 | }, 912 | "read-pkg": { 913 | "version": "1.1.0", 914 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", 915 | "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", 916 | "dev": true, 917 | "requires": { 918 | "load-json-file": "^1.0.0", 919 | "normalize-package-data": "^2.3.2", 920 | "path-type": "^1.0.0" 921 | } 922 | }, 923 | "read-pkg-up": { 924 | "version": "1.0.1", 925 | "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", 926 | "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", 927 | "dev": true, 928 | "requires": { 929 | "find-up": "^1.0.0", 930 | "read-pkg": "^1.0.0" 931 | } 932 | }, 933 | "readable-stream": { 934 | "version": "1.1.14", 935 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 936 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 937 | "dev": true, 938 | "requires": { 939 | "core-util-is": "~1.0.0", 940 | "inherits": "~2.0.1", 941 | "isarray": "0.0.1", 942 | "string_decoder": "~0.10.x" 943 | } 944 | }, 945 | "redent": { 946 | "version": "1.0.0", 947 | "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", 948 | "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", 949 | "dev": true, 950 | "requires": { 951 | "indent-string": "^2.1.0", 952 | "strip-indent": "^1.0.1" 953 | } 954 | }, 955 | "repeating": { 956 | "version": "2.0.1", 957 | "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", 958 | "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", 959 | "dev": true, 960 | "requires": { 961 | "is-finite": "^1.0.0" 962 | } 963 | }, 964 | "request": { 965 | "version": "2.88.2", 966 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", 967 | "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", 968 | "dev": true, 969 | "requires": { 970 | "aws-sign2": "~0.7.0", 971 | "aws4": "^1.8.0", 972 | "caseless": "~0.12.0", 973 | "combined-stream": "~1.0.6", 974 | "extend": "~3.0.2", 975 | "forever-agent": "~0.6.1", 976 | "form-data": "~2.3.2", 977 | "har-validator": "~5.1.3", 978 | "http-signature": "~1.2.0", 979 | "is-typedarray": "~1.0.0", 980 | "isstream": "~0.1.2", 981 | "json-stringify-safe": "~5.0.1", 982 | "mime-types": "~2.1.19", 983 | "oauth-sign": "~0.9.0", 984 | "performance-now": "^2.1.0", 985 | "qs": "~6.5.2", 986 | "safe-buffer": "^5.1.2", 987 | "tough-cookie": "~2.5.0", 988 | "tunnel-agent": "^0.6.0", 989 | "uuid": "^3.3.2" 990 | } 991 | }, 992 | "resolve": { 993 | "version": "1.17.0", 994 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", 995 | "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", 996 | "dev": true, 997 | "requires": { 998 | "path-parse": "^1.0.6" 999 | } 1000 | }, 1001 | "rimraf": { 1002 | "version": "2.7.1", 1003 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 1004 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 1005 | "dev": true, 1006 | "requires": { 1007 | "glob": "^7.1.3" 1008 | } 1009 | }, 1010 | "safe-buffer": { 1011 | "version": "5.2.1", 1012 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1013 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1014 | "dev": true 1015 | }, 1016 | "safer-buffer": { 1017 | "version": "2.1.2", 1018 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1019 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1020 | "dev": true 1021 | }, 1022 | "semver": { 1023 | "version": "5.7.1", 1024 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1025 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 1026 | "dev": true 1027 | }, 1028 | "signal-exit": { 1029 | "version": "3.0.3", 1030 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 1031 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", 1032 | "dev": true 1033 | }, 1034 | "single-line-log": { 1035 | "version": "1.1.2", 1036 | "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz", 1037 | "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=", 1038 | "dev": true, 1039 | "requires": { 1040 | "string-width": "^1.0.1" 1041 | } 1042 | }, 1043 | "spdx-correct": { 1044 | "version": "3.1.0", 1045 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", 1046 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", 1047 | "dev": true, 1048 | "requires": { 1049 | "spdx-expression-parse": "^3.0.0", 1050 | "spdx-license-ids": "^3.0.0" 1051 | } 1052 | }, 1053 | "spdx-exceptions": { 1054 | "version": "2.3.0", 1055 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", 1056 | "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", 1057 | "dev": true 1058 | }, 1059 | "spdx-expression-parse": { 1060 | "version": "3.0.1", 1061 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", 1062 | "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", 1063 | "dev": true, 1064 | "requires": { 1065 | "spdx-exceptions": "^2.1.0", 1066 | "spdx-license-ids": "^3.0.0" 1067 | } 1068 | }, 1069 | "spdx-license-ids": { 1070 | "version": "3.0.5", 1071 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", 1072 | "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", 1073 | "dev": true 1074 | }, 1075 | "speedometer": { 1076 | "version": "0.1.4", 1077 | "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz", 1078 | "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=", 1079 | "dev": true 1080 | }, 1081 | "sshpk": { 1082 | "version": "1.16.1", 1083 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 1084 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 1085 | "dev": true, 1086 | "requires": { 1087 | "asn1": "~0.2.3", 1088 | "assert-plus": "^1.0.0", 1089 | "bcrypt-pbkdf": "^1.0.0", 1090 | "dashdash": "^1.12.0", 1091 | "ecc-jsbn": "~0.1.1", 1092 | "getpass": "^0.1.1", 1093 | "jsbn": "~0.1.0", 1094 | "safer-buffer": "^2.0.2", 1095 | "tweetnacl": "~0.14.0" 1096 | } 1097 | }, 1098 | "string-width": { 1099 | "version": "1.0.2", 1100 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1101 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1102 | "dev": true, 1103 | "requires": { 1104 | "code-point-at": "^1.0.0", 1105 | "is-fullwidth-code-point": "^1.0.0", 1106 | "strip-ansi": "^3.0.0" 1107 | } 1108 | }, 1109 | "string_decoder": { 1110 | "version": "0.10.31", 1111 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 1112 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", 1113 | "dev": true 1114 | }, 1115 | "strip-ansi": { 1116 | "version": "3.0.1", 1117 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1118 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1119 | "dev": true, 1120 | "requires": { 1121 | "ansi-regex": "^2.0.0" 1122 | } 1123 | }, 1124 | "strip-bom": { 1125 | "version": "2.0.0", 1126 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", 1127 | "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", 1128 | "dev": true, 1129 | "requires": { 1130 | "is-utf8": "^0.2.0" 1131 | } 1132 | }, 1133 | "strip-indent": { 1134 | "version": "1.0.1", 1135 | "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", 1136 | "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", 1137 | "dev": true, 1138 | "requires": { 1139 | "get-stdin": "^4.0.1" 1140 | } 1141 | }, 1142 | "strip-json-comments": { 1143 | "version": "2.0.1", 1144 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1145 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 1146 | "dev": true 1147 | }, 1148 | "sumchecker": { 1149 | "version": "1.3.1", 1150 | "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-1.3.1.tgz", 1151 | "integrity": "sha1-ebs7RFbdBPGOvbwNcDodHa7FEF0=", 1152 | "dev": true, 1153 | "requires": { 1154 | "debug": "^2.2.0", 1155 | "es6-promise": "^4.0.5" 1156 | } 1157 | }, 1158 | "throttleit": { 1159 | "version": "0.0.2", 1160 | "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", 1161 | "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", 1162 | "dev": true 1163 | }, 1164 | "through2": { 1165 | "version": "0.2.3", 1166 | "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", 1167 | "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", 1168 | "dev": true, 1169 | "requires": { 1170 | "readable-stream": "~1.1.9", 1171 | "xtend": "~2.1.1" 1172 | } 1173 | }, 1174 | "tinymce": { 1175 | "version": "4.4.3", 1176 | "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.4.3.tgz", 1177 | "integrity": "sha1-Y98Ps/+Qrtq1BrfEUQNLteoZLLE=" 1178 | }, 1179 | "tough-cookie": { 1180 | "version": "2.5.0", 1181 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", 1182 | "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", 1183 | "dev": true, 1184 | "requires": { 1185 | "psl": "^1.1.28", 1186 | "punycode": "^2.1.1" 1187 | } 1188 | }, 1189 | "trim-newlines": { 1190 | "version": "1.0.0", 1191 | "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", 1192 | "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", 1193 | "dev": true 1194 | }, 1195 | "tunnel-agent": { 1196 | "version": "0.6.0", 1197 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1198 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 1199 | "dev": true, 1200 | "requires": { 1201 | "safe-buffer": "^5.0.1" 1202 | } 1203 | }, 1204 | "tweetnacl": { 1205 | "version": "0.14.5", 1206 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 1207 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 1208 | "dev": true 1209 | }, 1210 | "typedarray": { 1211 | "version": "0.0.6", 1212 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 1213 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", 1214 | "dev": true 1215 | }, 1216 | "uglify-js": { 1217 | "version": "1.3.4", 1218 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.3.4.tgz", 1219 | "integrity": "sha1-KCzsQNtWh5jg7G1x0MmJ0yPwY2s=", 1220 | "dev": true 1221 | }, 1222 | "underscore": { 1223 | "version": "1.3.3", 1224 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.3.3.tgz", 1225 | "integrity": "sha1-R6xTaD2vgyv6lS4XdEF9pHgXrkI=", 1226 | "dev": true 1227 | }, 1228 | "uri-js": { 1229 | "version": "4.2.2", 1230 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 1231 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 1232 | "dev": true, 1233 | "requires": { 1234 | "punycode": "^2.1.0" 1235 | } 1236 | }, 1237 | "util-deprecate": { 1238 | "version": "1.0.2", 1239 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1240 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 1241 | "dev": true 1242 | }, 1243 | "uuid": { 1244 | "version": "3.4.0", 1245 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 1246 | "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", 1247 | "dev": true 1248 | }, 1249 | "validate-npm-package-license": { 1250 | "version": "3.0.4", 1251 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 1252 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 1253 | "dev": true, 1254 | "requires": { 1255 | "spdx-correct": "^3.0.0", 1256 | "spdx-expression-parse": "^3.0.0" 1257 | } 1258 | }, 1259 | "verror": { 1260 | "version": "1.10.0", 1261 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 1262 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 1263 | "dev": true, 1264 | "requires": { 1265 | "assert-plus": "^1.0.0", 1266 | "core-util-is": "1.0.2", 1267 | "extsprintf": "^1.2.0" 1268 | } 1269 | }, 1270 | "wordwrap": { 1271 | "version": "0.0.3", 1272 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 1273 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", 1274 | "dev": true 1275 | }, 1276 | "wrappy": { 1277 | "version": "1.0.2", 1278 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1279 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1280 | "dev": true 1281 | }, 1282 | "xtend": { 1283 | "version": "2.1.2", 1284 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", 1285 | "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", 1286 | "dev": true, 1287 | "requires": { 1288 | "object-keys": "~0.4.0" 1289 | } 1290 | }, 1291 | "yauzl": { 1292 | "version": "2.10.0", 1293 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", 1294 | "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", 1295 | "dev": true, 1296 | "requires": { 1297 | "buffer-crc32": "~0.2.3", 1298 | "fd-slicer": "~1.1.0" 1299 | } 1300 | } 1301 | } 1302 | } 1303 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "impressionist", 3 | "author": "Henrik Ingo", 4 | "license": "MIT", 5 | "version": "0.1.0", 6 | "description": "A visual editor for creating 3D impress.js presentations", 7 | "homepage": "http://github.com/henrikingo/impressionist", 8 | "main": "main.js", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/henrikingo/impressionist.git", 12 | "github": "https://github.com/henrikingo/impressionist" 13 | }, 14 | "keywords": [ 15 | "presentation", 16 | "slides", 17 | "slideshow", 18 | "css3", 19 | "3D", 20 | "transforms", 21 | "browser", 22 | "GUI", 23 | "editor" 24 | ], 25 | "bugs": { 26 | "url": "https://github.com/henrikingo/impressionist/issues" 27 | }, 28 | "scripts": { 29 | "build": "node build.js", 30 | "start": "electron ." 31 | }, 32 | "dependencies": { 33 | "tinymce": "4.4.3" 34 | }, 35 | "devDependencies": { 36 | "buildify": "0.4.0", 37 | "electron": "1.4.4" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/impressionist.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS styles for Impressionist presentations editor 3 | * 4 | * Copyright 2016, Henrik Ingo (@henrikingo) 5 | * MIT License 6 | */ 7 | -------------------------------------------------------------------------------- /src/impressionist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Impressionist.js - A visual editor for impress.js 3 | * 4 | * This is the main JS file for the browser / renderer process side of Impressionist. By running 5 | * `node build.js` this is concatenated with all the `src/plugins/*` into `js/impressionist.js`, 6 | * which is the file actually used in a browser / Electron renderer process. 7 | * 8 | * This file simply exposes a global function `impressionist()`, which returns an object that is 9 | * the impressionist api. This is exactly analogous to how `impress()` returns the impress api. 10 | * 11 | * Currently this file doesn't include any core functionality or interesting api. The only 12 | * functions you can actually call in the api are common utility functions like 13 | * `impressionist().util.toNumber()`. 14 | * 15 | * Henrik Ingo (c) 2016 16 | * MIT License 17 | */ 18 | (function ( document, window ) { 19 | 'use strict'; 20 | 21 | // Populated by separate library plugins, see src/lib/* 22 | var impressionistApi = {}; 23 | 24 | window.impressionist = function(){ 25 | return impressionistApi; 26 | }; 27 | 28 | })(document, window); 29 | -------------------------------------------------------------------------------- /src/lib/css3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper functions to create CSS3 strings 3 | * 4 | * Henrik Ingo (c) 2016 5 | * MIT License 6 | * 7 | * Mostly copied from impress.js, same license. 8 | */ 9 | (function ( document, window ) { 10 | 'use strict'; 11 | 12 | if( impressionist().css3 === undefined ){ 13 | impressionist().css3 = {} 14 | } 15 | 16 | // `translate` builds a translate transform string for given data. 17 | impressionist().css3.translate = function ( t ) { 18 | return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) "; 19 | }; 20 | 21 | // `rotate` builds a rotate transform string for given data. 22 | // By default the rotations are in X Y Z order that can be reverted by passing `true` 23 | // as second parameter. 24 | impressionist().css3.rotate = function ( r, revert ) { 25 | var order = r.order ? r.order : "xyz"; 26 | var css = ""; 27 | var axes = order.split(""); 28 | if ( revert ) { 29 | axes = axes.reverse(); 30 | } 31 | 32 | for ( var i in axes ) { 33 | css += " rotate" + axes[i].toUpperCase() + "(" + r[axes[i]] + "deg)" 34 | } 35 | return css; 36 | }; 37 | 38 | // `scale` builds a scale transform string for given data. 39 | impressionist().css3.scale = function ( s ) { 40 | return " scale(" + s + ") "; 41 | }; 42 | 43 | // `perspective` builds a perspective transform string for given data. 44 | impressionist().css3.perspective = function ( p ) { 45 | return " perspective(" + p + "px) "; 46 | }; 47 | 48 | // `css` function applies the styles given in `props` object to the element 49 | // given as `el`. It runs all property names through `pfx` function to make 50 | // sure proper prefixed version of the property is used. 51 | impressionist().css3.css = function ( el, props ) { 52 | var key, pkey; 53 | for ( key in props ) { 54 | if ( props.hasOwnProperty(key) ) { 55 | pkey = key; 56 | if ( pkey !== null ) { 57 | el.style[pkey] = props[key]; 58 | } 59 | } 60 | } 61 | return el; 62 | }; 63 | 64 | impressionist().css3.computeWindowScale = function ( config ) { 65 | var hScale = window.innerHeight / config.height, 66 | wScale = window.innerWidth / config.width, 67 | scale = hScale > wScale ? wScale : hScale; 68 | if (config.maxScale && scale > config.maxScale) { 69 | scale = config.maxScale; 70 | } 71 | if (config.minScale && scale < config.minScale) { 72 | scale = config.minScale; 73 | } 74 | return scale; 75 | }; 76 | 77 | })(document, window); 78 | -------------------------------------------------------------------------------- /src/lib/gc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Garbage collection utility 3 | * 4 | * Impressionist features that add their own elements to the DOM of a presentation, can register 5 | * those elements with the garbage collector. The garbage collector will then remove them when 6 | * the document is saved, so that there's no trace of impressionist left in the document. 7 | * 8 | * Henrik Ingo (c) 2016 9 | * MIT License 10 | */ 11 | (function ( document, window ) { 12 | 'use strict'; 13 | 14 | var elementList = []; 15 | var eventListenerList = []; 16 | var id = Math.random(); 17 | 18 | if( impressionist().gc === undefined ){ 19 | impressionist().gc = {} 20 | } 21 | 22 | impressionist().gc.pushElement = function ( element ) { 23 | elementList.push(element); 24 | }; 25 | 26 | // Convenience wrapper that combines DOM appendChild with gc.pushElement 27 | impressionist().gc.appendChild = function ( parent, element ) { 28 | parent.appendChild(element); 29 | impressionist().gc.pushElement(element); 30 | }; 31 | 32 | impressionist().gc.pushEventListener = function ( target, type, listenerFunction ) { 33 | eventListenerList.push( {target:target, type:type, listener:listenerFunction} ); 34 | }; 35 | 36 | // Convenience wrapper that combines DOM addEventListener with gc.pushEventListener 37 | impressionist().gc.addEventListener = function ( target, type, listenerFunction ) { 38 | target.addEventListener( type, listenerFunction ); 39 | impressionist().gc.pushEventListener( target, type, listenerFunction ); 40 | }; 41 | 42 | impressionist().gc.removeAll = function () { 43 | tinymceCssHack(); 44 | for ( var i in elementList ) { 45 | elementList[i].parentElement.removeChild(elementList[i]); 46 | } 47 | elementList = []; 48 | for ( var i in eventListenerList ) { 49 | var target = eventListenerList[i].target; 50 | var type = eventListenerList[i].type; 51 | var listener = eventListenerList[i].listener; 52 | target.removeEventListener(type, listener); 53 | } 54 | }; 55 | 56 | // These css are added by tinymce asynchronously, and it doesn't provide a callback 57 | // api where I could do this when they're added. So we just capture them here, right before 58 | // we're going to call removeChild() on them. 59 | var tinymceCssHack = function () { 60 | var css1 = "skins/lightgray/skin.min.css"; 61 | var css2 = "skins/lightgray/content.inline.min.css"; 62 | var links = document.head.querySelectorAll("link"); 63 | for (var i = 0; i < links.length; i++){ 64 | var l = links[i]; 65 | if( l.href.substring( l.href.length - css1.length ) == css1 || 66 | l.href.substring( l.href.length - css2.length ) == css2 ){ 67 | impressionist().gc.pushElement(l); 68 | } 69 | } 70 | 71 | var mceElements = document.querySelectorAll(".mce-content-body") 72 | for ( var i = 0; i < mceElements.length; i++ ) { 73 | mceElements[i].classList.remove("mce-content-body"); 74 | mceElements[i].classList.remove("mce-edit-focus"); 75 | mceElements[i].removeAttribute("contenteditable"); 76 | mceElements[i].removeAttribute("spellcheck"); 77 | } 78 | var mceElements = document.querySelectorAll("br") 79 | for ( var i = 0; i < mceElements.length; i++ ) { 80 | if ( mceElements[i].getAttribute("data-mce-bogus") == "1" ) { 81 | mceElements[i].parentElement.removeChild(mceElements[i]); 82 | } 83 | } 84 | var mceElements = document.querySelectorAll(".mce-widget") 85 | for ( var i = 0; i < mceElements.length; i++ ) { 86 | mceElements[i].parentElement.removeChild(mceElements[i]); 87 | } 88 | var mceElements = document.querySelectorAll(".mce-container") 89 | for ( var i = 0; i < mceElements.length; i++ ) { 90 | mceElements[i].parentElement.removeChild(mceElements[i]); 91 | } 92 | document.body.removeAttribute("spellcheck"); 93 | 94 | var style = document.getElementById("mceDefaultStyles"); 95 | impressionist().gc.pushElement(style); 96 | }; 97 | 98 | })(document, window); 99 | -------------------------------------------------------------------------------- /src/lib/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utilities library functions 3 | * 4 | * Henrik Ingo (c) 2016 5 | * MIT License 6 | * 7 | * Parts copied from impress.js, same license. 8 | */ 9 | (function ( document, window ) { 10 | 'use strict'; 11 | 12 | if( impressionist().util === undefined ){ 13 | impressionist().util = {}; 14 | } 15 | 16 | impressionist().util.capitalize = function( str ) { 17 | return str[0].toUpperCase() + str.substring(1); 18 | } 19 | 20 | impressionist().util.toNumber = function (numeric, fallback) { 21 | return isNaN(numeric) ? (fallback || 0) : Number(numeric); 22 | }; 23 | 24 | impressionist().util.toOrder = function ( order, fallback ) { 25 | fallback = fallback || "xyz"; 26 | if ( ! order ) return fallback; 27 | if ( order.length > 3 ) return fallback; 28 | for ( var i = 0; i < order.length; i++ ) { 29 | var c = order[i]; 30 | if ( "xyz".indexOf(c) < 0 ) return fallback; 31 | } 32 | return order; 33 | }; 34 | 35 | impressionist().util.triggerEvent = function (el, eventName, detail) { 36 | var event = document.createEvent("CustomEvent"); 37 | event.initCustomEvent(eventName, true, true, detail); 38 | el.dispatchEvent(event); 39 | }; 40 | 41 | impressionist().util.makeDomElement = function ( html ) { 42 | var tempDiv = document.createElement("div"); 43 | tempDiv.innerHTML = html; 44 | return tempDiv.firstChild; 45 | }; 46 | 47 | impressionist().util.loadJavaScript = function ( url, callback ) { 48 | var script = document.createElement("script"); 49 | script.src = url; 50 | script.type = "text/javascript"; 51 | script.onreadystatechange = callback; 52 | script.onload = callback; 53 | document.head.appendChild(script); 54 | return script; 55 | }; 56 | 57 | impressionist().util.loadCss = function ( url, callback ) { 58 | var link = document.createElement("link"); 59 | link.href = url; 60 | link.rel = "stylesheet"; 61 | link.type = "text/css"; 62 | link.onreadystatechange = callback; 63 | link.onload = callback; 64 | document.head.appendChild(link); 65 | return link; 66 | }; 67 | 68 | 69 | impressionist().util.getSelectValues = function (select) { 70 | var result = []; 71 | var options = select.options; 72 | 73 | for (var i=0; i < options.length; i++) { 74 | if (options[i].selected) { 75 | result.push(options[i].value); 76 | } 77 | } 78 | return result; 79 | }; 80 | 81 | if( impressionist().util.array === undefined ){ 82 | impressionist().util.array = {}; 83 | } 84 | 85 | impressionist().util.array.intersection = function (a, b) { 86 | return a.filter(function(value) { 87 | return b.indexOf(value) > -1; 88 | }); 89 | }; 90 | 91 | impressionist().util.array.minus = function (a, b) { 92 | return a.filter(function(value) { 93 | return b.indexOf(value) == -1; 94 | }); 95 | }; 96 | 97 | })(document, window); 98 | -------------------------------------------------------------------------------- /src/main/README.md: -------------------------------------------------------------------------------- 1 | Files required from the Electron main.js process 2 | -------------------------------------------------------------------------------- /src/main/fileOpenSave.js: -------------------------------------------------------------------------------- 1 | /* File open and save. (Dialogs and implementation) */ 2 | const electron = require('electron') 3 | const app = electron.app 4 | 5 | 6 | 7 | let mainWindowPointer = null 8 | let currentFile = "" 9 | const dialog = require('electron').dialog 10 | 11 | var fileOpen = function(){ 12 | dialog.showOpenDialog({ 13 | title: 'Open Presentation', 14 | filters: [ 15 | { name: 'Impress Presentation (HTML)', extensions: ['html'] } 16 | ], 17 | properties: ['openFile'] 18 | }, function (files) { 19 | if (files) { 20 | currentFile = files[0] 21 | mainWindowPointer.loadURL('file://' + currentFile) 22 | } 23 | }) 24 | }; 25 | 26 | const ipc = require('electron').ipcMain 27 | const fs = require('fs') 28 | var fileSave = function() { 29 | mainWindowPointer.webContents.send('impressionist-get-documentElement', currentFile) 30 | } 31 | 32 | var fileSaveAs = function() { 33 | dialog.showSaveDialog({ 34 | title: 'Save Presentation', 35 | filters: [ 36 | { name: 'Impress Presentation (HTML)', extensions: ['html'] } 37 | ] 38 | }, function (filename) { 39 | if (filename) { 40 | currentFile = filename 41 | mainWindowPointer.webContents.send('impressionist-get-documentElement', currentFile) 42 | } 43 | }) 44 | } 45 | ipc.on('impressionist-return-documentElement', function(event, data){ 46 | fs.writeFile(data.filename, data.documentElement + "\n", function (err) { 47 | if(err){ 48 | // TODO: Show a dialog box 49 | console.log("An error ocurred creating the file "+ err.message) 50 | } 51 | }) 52 | }) 53 | 54 | module.exports = { 55 | init: function(mainWindow){ 56 | mainWindowPointer = mainWindow 57 | return { 58 | open: fileOpen, 59 | save: fileSave, 60 | saveAs: fileSaveAs 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/menu.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron') 2 | const app = electron.app 3 | const Menu = electron.Menu 4 | 5 | /* Menu */ 6 | module.exports = { 7 | createMenu: function(mainWindow){ 8 | let file = require('./fileOpenSave').init(mainWindow) 9 | 10 | var menuLabel = "&File" 11 | if (process.platform === 'darwin') { 12 | menuLabel = electron.app.getName() 13 | } 14 | 15 | let template = [{ 16 | label: menuLabel, 17 | submenu: [{ 18 | label: '&Open', 19 | accelerator: 'CmdOrCtrl+O', 20 | click: file.open 21 | }, { 22 | label: '&Save', 23 | accelerator: 'CmdOrCtrl+S', 24 | click: file.save 25 | }, { 26 | label: 'Save &As...', 27 | accelerator: 'Shift+CmdOrCtrl+S', 28 | click: file.saveAs 29 | }, { 30 | label: 'Quit', 31 | accelerator: 'CmdOrCtrl+Q', 32 | role: 'quit' 33 | }] 34 | }] 35 | 36 | const menu = Menu.buildFromTemplate(template) 37 | Menu.setApplicationMenu(menu) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/plugins/axis/axis.css: -------------------------------------------------------------------------------- 1 | /* Axis plugin */ 2 | #impressionist-axis-x, 3 | #impressionist-axis-y, 4 | #impressionist-axis-z { 5 | border-bottom: dashed 20px; 6 | height: 0px; 7 | padding: 0px; 8 | width: 10000px; 9 | /* The axis are steps, but we don't want you to click on them to move to them. */ 10 | pointer-events: none; 11 | opacity: 0.6; 12 | } 13 | 14 | #impressionist-axis-x { 15 | border-color: red; 16 | } 17 | 18 | #impressionist-axis-y { 19 | border-color: blue; 20 | } 21 | 22 | #impressionist-axis-z { 23 | border-color: purple; 24 | } 25 | -------------------------------------------------------------------------------- /src/plugins/axis/axis.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Axis plugin 3 | * 4 | * Draw x, y and z axis to help user navigate as we move the camera around. 5 | * 6 | * Copyright 2016 Henrik Ingo (@henrikingo) 7 | * Released under the MIT license. 8 | */ 9 | (function ( document, window ) { 10 | 'use strict'; 11 | var root; 12 | var gc = impressionist().gc; 13 | 14 | var triggerEvent = function (el, eventName, detail) { 15 | var event = document.createEvent("CustomEvent"); 16 | event.initCustomEvent(eventName, true, true, detail); 17 | el.dispatchEvent(event); 18 | }; 19 | 20 | var makeDomElement = function ( html ) { 21 | var tempDiv = document.createElement("div"); 22 | tempDiv.innerHTML = html; 23 | return tempDiv.firstChild; 24 | }; 25 | 26 | root = document.getElementById("impress"); 27 | 28 | // Must set data-x/y/z explicitly to zero for presentations that might use relative positioning 29 | // Use .skip to omit these steps from navigation, and impress:navigation-ui:hideStep to hide them from the select widget. 30 | var x = makeDomElement( '
' ); 32 | gc.appendChild(root, x); 33 | var y = makeDomElement( '' ); 35 | gc.appendChild(root, y); 36 | var z = makeDomElement( '' ); 38 | gc.appendChild(root, z); 39 | 40 | // Wait until everyone is initialized before trying to communicate API calls 41 | gc.addEventListener(document, "impressionist:init", function( event ){ 42 | triggerEvent( x, "impress:navigation-ui:hideStep", {} ); 43 | triggerEvent( y, "impress:navigation-ui:hideStep", {} ); 44 | triggerEvent( z, "impress:navigation-ui:hideStep", {} ); 45 | }); 46 | 47 | })(document, window); -------------------------------------------------------------------------------- /src/plugins/camera/camera.css: -------------------------------------------------------------------------------- 1 | /* Camera plugin */ 2 | 3 | #impressionist-toolbar .impressionist-camera-minus, 4 | #impressionist-toolbar .impressionist-camera-plus, 5 | #impressionist-toolbar .impressionist-step-minus, 6 | #impressionist-toolbar .impressionist-step-plus { 7 | font-size: 70%; 8 | width: 25px; 9 | height: 15px; 10 | padding: 0; 11 | border: 0; 12 | background: none; 13 | 14 | position: relative; 15 | top: 15px; 16 | left: -47px; 17 | } 18 | 19 | /* Cut away the empty space left by the plus and minus buttons as they were relatively positioned elsewhere. */ 20 | #impressionist-toolbar-group-1 span, 21 | #impressionist-toolbar-group-2 span { 22 | margin-right: -48px; 23 | } 24 | #impressionist-toolbar-group-1 span.nocut, 25 | #impressionist-toolbar-group-2 span.nocut { 26 | margin-right: none; 27 | } 28 | -------------------------------------------------------------------------------- /src/plugins/camera/camera.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Camera plugin 3 | * 4 | * The camera allows to navigate and view your presentation from an arbitrary angle and scaling. 5 | * 6 | * Copyright 2016 Henrik Ingo (@henrikingo) 7 | * Released under the MIT license. 8 | */ 9 | (function ( document, window ) { 10 | 'use strict'; 11 | var toolbar; 12 | var coordinates = {}; 13 | coordinates["camera"] = {rotate:{x:0,y:0,z:0},translate:{x:0,y:0,z:0,order:"xyz"},scale:1}; 14 | coordinates["step"] = {rotate:{x:0,y:0,z:0},translate:{x:0,y:0,z:0,order:"xyz"},scale:1}; 15 | var cameraWidgets = {}; 16 | var stepWidgets = {}; 17 | var widgetNames = ['x', 'y', 'z', 'scale', 'rotateX', 'rotateY', 'rotateZ', 'order']; 18 | var activeStep; 19 | var util = impressionist().util; 20 | var css3 = impressionist().css3; 21 | 22 | // Generally impress.js & impressionist don't use object oriented JavaScript, but 23 | // this is definitively sliding in that direction... 24 | 25 | // Helper function to set the right path in coordinates[*], given a name from widgetNames 26 | var setCoordinate = function( cameraOrStep, name, value ) { 27 | // Set the named coordinate for both, if the lock widget is checked, otherwise only for cameraOrStep 28 | // By default, just update the one supplied 29 | var todo = [cameraOrStep]; 30 | if (cameraStepLocked()) { 31 | // If lock is checked, update both 32 | todo = ["camera", "step"]; 33 | }; 34 | 35 | for (var i=0; i < todo.length; i++) { 36 | if ( name.length == 1 ) { // x, y, z 37 | coordinates[todo[i]].translate[name] = value; 38 | } 39 | else if ( name == "scale" ) { 40 | coordinates[todo[i]].scale = value; 41 | } 42 | else if ( name == "order" ) { 43 | coordinates[todo[i]].rotate.order = value; 44 | } 45 | else { 46 | var xyz = name.substr(-1).toLowerCase(); 47 | coordinates[todo[i]].rotate[xyz] = value; 48 | } 49 | } 50 | }; 51 | // Helper function to get the right path in coordinates[*] object, given a name from widgetNames 52 | var getCoordinate = function( cameraOrStep, name ) { 53 | if ( name.length == 1 ) { // x, y, z 54 | return coordinates[cameraOrStep].translate[name]; 55 | } 56 | else if ( name == "scale" ) { 57 | return coordinates[cameraOrStep].scale; 58 | } 59 | else if ( name == "order" ) { 60 | return coordinates[cameraOrStep].rotate.order; 61 | } 62 | else { 63 | var xyz = name.substr(-1).toLowerCase(); 64 | return coordinates[cameraOrStep].rotate[xyz]; 65 | } 66 | }; 67 | 68 | // Move canvas (aka the camera) to match the coordinates["camera"] 69 | var updateCanvasPosition = function(transitionDuration, oldScale) { 70 | var duration = transitionDuration || 0; 71 | var delay = (duration / 2); 72 | 73 | var root = document.getElementById("impress"); 74 | var rootData = root.dataset; 75 | var config = { 76 | width: util.toNumber( rootData.width, 1024 ), 77 | height: util.toNumber( rootData.height, 768 ), 78 | maxScale: util.toNumber( rootData.maxScale, 1 ), 79 | minScale: util.toNumber( rootData.minScale, 0 ), 80 | perspective: util.toNumber( rootData.perspective, 1000 ) 81 | }; 82 | var canvas = root.firstChild; 83 | 84 | // compute target state of the canvas based on given step 85 | var target = { 86 | rotate: { 87 | x: -coordinates["camera"].rotate.x, 88 | y: -coordinates["camera"].rotate.y, 89 | z: -coordinates["camera"].rotate.z, 90 | order: coordinates["camera"].rotate.order 91 | }, 92 | translate: { 93 | x: -coordinates["camera"].translate.x, 94 | y: -coordinates["camera"].translate.y, 95 | z: -coordinates["camera"].translate.z 96 | }, 97 | scale: 1 / coordinates["camera"].scale 98 | }; 99 | 100 | var windowScale = css3.computeWindowScale(config); 101 | var targetScale = target.scale * windowScale; 102 | var zoomin = target.scale >= oldScale; // Copied from impress.js, elaborate commentary over there. 103 | 104 | css3.css(root, { 105 | // to keep the perspective look similar for different scales 106 | // we need to 'scale' the perspective, too 107 | transform: css3.perspective( config.perspective / targetScale ) + css3.scale( targetScale ), 108 | transitionDuration: duration + "ms", 109 | transitionDelay: (zoomin ? delay : 0) + "ms" 110 | }); 111 | css3.css(canvas, { 112 | transform: css3.rotate(target.rotate, true) + css3.translate(target.translate), 113 | transitionDuration: duration + "ms", 114 | transitionDelay: (zoomin ? 0 : delay) + "ms" 115 | }); 116 | }; 117 | 118 | var updateStepPosition = function () { 119 | // First we want to persist the new coordinates to the dom element of the active step. 120 | activeStep.setAttribute("data-x", coordinates["step"].translate.x); 121 | activeStep.setAttribute("data-y", coordinates["step"].translate.y); 122 | activeStep.setAttribute("data-z", coordinates["step"].translate.z); 123 | activeStep.setAttribute("data-rotate-x", coordinates["step"].rotate.x); 124 | activeStep.setAttribute("data-rotate-y", coordinates["step"].rotate.y); 125 | activeStep.setAttribute("data-rotate-z", coordinates["step"].rotate.z); 126 | activeStep.setAttribute("data-rotate-order", coordinates["step"].rotate.order); 127 | activeStep.setAttribute("data-scale", coordinates["step"].scale); 128 | 129 | // Then set the 3D CSS to match. This actually moves the step. 130 | css3.css(activeStep, { 131 | position: "absolute", 132 | transform: "translate(-50%,-50%)" + 133 | css3.translate(coordinates["step"].translate) + 134 | css3.rotate(coordinates["step"].rotate) + 135 | css3.scale(coordinates["step"].scale), 136 | transformStyle: "preserve-3d" 137 | }); 138 | }; 139 | 140 | // Set event listeners for widgets.x.input/plus/minus widgets. 141 | var setListeners = function( widgets, name, cameraOrStep ){ 142 | if (name == "order") return; // The last widget is non-numeric, separate listeners set explicitly. 143 | 144 | widgets[name].input.addEventListener( "input", function( event ) { 145 | setCoordinate( cameraOrStep, name, util.toNumber( event.target.value, name=="scale"?1:0 ) ); 146 | updateCanvasPosition(); 147 | updateStepPosition(); 148 | }); 149 | widgets[name].minus.addEventListener( "click", function( event ) { 150 | setCoordinate( cameraOrStep, name, Math.round(getCoordinate(cameraOrStep, name)-1) ); 151 | // But scale cannot be < 1 152 | if( name == "scale" && getCoordinate( cameraOrStep, name ) < 1 ) 153 | setCoordinate( cameraOrStep, name, 1 ); 154 | updateWidgets(); 155 | updateCanvasPosition(); 156 | updateStepPosition(); 157 | }); 158 | widgets[name].plus.addEventListener( "click", function( event ) { 159 | setCoordinate( cameraOrStep, name, Math.round(getCoordinate(cameraOrStep, name)+1) ); 160 | updateWidgets(); 161 | updateCanvasPosition(); 162 | updateStepPosition(); 163 | }); 164 | }; 165 | 166 | // order widget has its own listeners, as it's not a numeric field 167 | var setOrderListeners = function( widgets, cameraOrStep ) { 168 | var name = "order"; 169 | widgets[name].input.addEventListener( "input", function( event ) { 170 | var v = event.target.value.toString().toLowerCase(); 171 | var value = ""; 172 | for (var i = 0; i < Math.min(v.length, 3); i++){ 173 | if( v[i] != "x" && v[i] != "y" && v[i] != "z" ){ 174 | continue; 175 | } 176 | value += v[i]; 177 | } 178 | event.target.value = value; 179 | setCoordinate( cameraOrStep, name, value ); 180 | updateCanvasPosition(); 181 | updateStepPosition(); 182 | }); 183 | widgets[name].minus.addEventListener( "click", function( event ) { 184 | var current = getCoordinate(cameraOrStep, name); 185 | var value = ""; 186 | if( current.length < 3 ) { 187 | var available = "xyz"; 188 | for( var i=0; i < current.length; i++ ) { 189 | // Remove the letters already in the text field from available 190 | available = available.split(current[i]).join(""); 191 | } 192 | value = current + available[0]; 193 | } 194 | else { 195 | // shift the order string so that 1st letter becomes last, second first, third second. 196 | value = current[1] + current[2] + current[0]; 197 | } 198 | setCoordinate( cameraOrStep, name, value ); 199 | updateWidgets(); 200 | updateCanvasPosition(); 201 | updateStepPosition(); 202 | }); 203 | widgets[name].plus.addEventListener( "click", function( event ) { 204 | var current = getCoordinate(cameraOrStep, name); 205 | var value = ""; 206 | if( current.length < 3 ) { 207 | var available = "xyz"; 208 | for( var i=0; i < current.length; i++ ) { 209 | // Remove the letters already in the text field from available 210 | available = available.split(current[i]).join(""); 211 | } 212 | value = available[0] + current; 213 | } 214 | else { 215 | // shift the order string so that 1st letter becomes last, second first, third second. 216 | value = current[2] + current[0] + current[1]; 217 | } 218 | setCoordinate( cameraOrStep, name, value ); 219 | updateWidgets(); 220 | updateCanvasPosition(); 221 | updateStepPosition(); 222 | }); 223 | }; 224 | 225 | // The lock listener does 2 things 226 | // 1. Force the checkboxes on the camera and step tab to be in sync. If one is checked, the other is too. 227 | // 2. Update widgets on Camera tab to match the coordinates of the current step 228 | var setLockListener = function( widgets, cameraOrStep ) { 229 | widgets["lock"].input.addEventListener( "click", function( event ) { 230 | // Sync both checkboxes to the value that was set with this click 231 | var value = event.target.checked; 232 | cameraWidgets["lock"].input.checked = value; 233 | stepWidgets["lock"].input.checked = value; 234 | // Update widgets with values from current step 235 | if ( value == true ) { 236 | activeStep = document.querySelector("#impress .step.active"); 237 | // Need to save the old scale value to be used for a nice zoom delay. 238 | // (As impress.js does. Most of camera.js moves around with controls, but here we 239 | // want to animate the transition so that user doesn't suddenly jump to a weird place.) 240 | var oldScale = getCoordinate( "camera", "scale" ); 241 | getActiveStepCoordinates(activeStep); 242 | updateWidgets(); 243 | updateCanvasPosition(1000, oldScale); 244 | } 245 | }); 246 | }; 247 | 248 | var createControls = function(cameraOrStep, group, widgets) { 249 | // Set the text of the tab for this group of widgets 250 | util.triggerEvent(toolbar, "impressionist:toolbar:groupTitle", { group: group, title: util.capitalize(cameraOrStep) } ) 251 | 252 | widgetNames.forEach( function(name){ 253 | var r = name == "rotateX" ? "rotate: " : ""; 254 | var label = name.substr(0,6)=="rotate" ? name.substr(-1).toLowerCase() : name; 255 | var element = util.makeDomElement( '' + r + label + 256 | ':' + 258 | '' + 260 | ' ' ); 262 | util.triggerEvent(toolbar, "impressionist:toolbar:appendChild", { group : group, element : element } ); 263 | 264 | var input = element.firstElementChild; 265 | var minus = input.nextSibling; 266 | var plus = minus.nextSibling; 267 | widgets[name] = { minus : minus, input : input, plus : plus }; 268 | setListeners( widgets, name, cameraOrStep ); 269 | }); 270 | setOrderListeners( widgets, cameraOrStep ); 271 | 272 | // Add a checkbox to control whether the + O Z cameracontrols will control camera, step or both 273 | var span = util.makeDomElement( 'lock:' + 274 | '' + 276 | '' ); 277 | util.triggerEvent(toolbar, "impressionist:toolbar:appendChild", { group: group, element: span }); 278 | widgets["lock"] = { input : span.firstElementChild }; 279 | setLockListener( widgets, cameraOrStep ); 280 | }; 281 | 282 | // Update the coordinates objects from the currently activeStep. 283 | // IOW this assumes that the canvas positioning in fact matches the attributes of the activeStep 284 | // which is at least true for example immediately after impress:stepenter event. 285 | var getActiveStepCoordinates = function(activeStep) { 286 | var stepData = activeStep.dataset; 287 | coordinates["camera"] = { 288 | rotate: { 289 | x: util.toNumber(stepData.rotateX), 290 | y: util.toNumber(stepData.rotateY), 291 | z: util.toNumber(stepData.rotateZ, util.toNumber(stepData.rotate)), 292 | order: util.toOrder(stepData.rotateOrder) 293 | }, 294 | translate: { 295 | x: util.toNumber(stepData.x), 296 | y: util.toNumber(stepData.y), 297 | z: util.toNumber(stepData.z) 298 | }, 299 | scale: util.toNumber(stepData.scale, 1) 300 | }; 301 | 302 | coordinates["step"] = { 303 | rotate: { 304 | x: util.toNumber(stepData.rotateX), 305 | y: util.toNumber(stepData.rotateY), 306 | z: util.toNumber(stepData.rotateZ, util.toNumber(stepData.rotate)), 307 | order: util.toOrder(stepData.rotateOrder) 308 | }, 309 | translate: { 310 | x: util.toNumber(stepData.x), 311 | y: util.toNumber(stepData.y), 312 | z: util.toNumber(stepData.z) 313 | }, 314 | scale: util.toNumber(stepData.scale, 1) 315 | }; 316 | }; 317 | 318 | var updateWidgets = function() { 319 | widgetNames.forEach( function( name ) { 320 | cameraWidgets[name].input.value = getCoordinate("camera", name); 321 | stepWidgets[name].input.value = getCoordinate("step", name); 322 | }); 323 | }; 324 | 325 | // Get active toolbar tab between "camera" and "step". If neither "camera" or "step" is active, return "camera" 326 | var getActiveTab = function() { 327 | var selectedTab = document.querySelector("#impressionist-toolbar-titles button.selected"); 328 | if (selectedTab.innerHTML == "Step") { 329 | return "step"; 330 | } 331 | return "camera"; 332 | }; 333 | 334 | // Returns true if the lock checkbox is checked, otherwise false 335 | var cameraStepLocked = function() { 336 | // cameraWidgets["lock"] and stepWidgets["lock"] are always in sync, doesn't matter which one we check 337 | if (cameraWidgets["lock"].input.checked) { 338 | return true; 339 | } 340 | else { 341 | return false; 342 | } 343 | }; 344 | 345 | // API for other plugins to move the camera position /////////////////////////////////////////// 346 | var gc = impressionist().gc; 347 | gc.addEventListener(document, "impressionist:camera:setCoordinates", function (event) { 348 | var moveTo = event.detail; 349 | var activeTab = getActiveTab(); 350 | widgetNames.forEach( function( name ) { 351 | if ( moveTo[name] === undefined ) return; // continue, but in JS forEach is a function 352 | if ( name == "order" ) { 353 | setCoordinate( activeTab, name, util.toOrder(moveTo[name], getCoordinate("camera", name) ) ); 354 | } 355 | else { 356 | setCoordinate( activeTab, name, util.toNumber( moveTo[name], getCoordinate("camera", name) ) ); 357 | } 358 | }); 359 | updateWidgets(); 360 | updateCanvasPosition(); 361 | updateStepPosition(); 362 | }); 363 | 364 | // impressionist and impress.js events /////////////////////////////////////////////////////// 365 | 366 | gc.addEventListener(document, "impressionist:toolbar:init", function (event) { 367 | toolbar = event.detail.toolbar; 368 | 369 | createControls( "camera", 1, cameraWidgets ); 370 | util.triggerEvent( toolbar, "impressionist:camera:init", { "widgets" : cameraWidgets } ); 371 | createControls( "step", 2, stepWidgets ); 372 | util.triggerEvent( toolbar, "impressionist:stepmove:init", { "widgets" : stepWidgets } ); 373 | 374 | activeStep = document.querySelector("#impress .step.active"); 375 | getActiveStepCoordinates(activeStep); 376 | updateWidgets(); 377 | }); 378 | 379 | // If user moves to another step with impress().prev() / .next() or .goto(), then the canvas 380 | // will be set according to that step. We update our widgets to reflect reality. 381 | // From here, user can again zoom out or pan away as he prefers. 382 | gc.addEventListener(document, "impress:stepenter", function (event) { 383 | activeStep = event.target; 384 | getActiveStepCoordinates(activeStep); 385 | updateWidgets(); 386 | }); 387 | 388 | // impress.js also resets the css coordinates when a window is resized event. Wait a second, 389 | // then update widgets to match reality. 390 | gc.addEventListener(window, "resize", function () { 391 | window.setTimeout( function(){ 392 | getActiveStepCoordinates(activeStep); 393 | updateWidgets(); 394 | }, 1000 ); 395 | }); 396 | 397 | })(document, window); 398 | 399 | -------------------------------------------------------------------------------- /src/plugins/cameracontrols/cameracontrols.css: -------------------------------------------------------------------------------- 1 | /* Cameracontrols plugin (the buttons that allow you to pan, zoom, scale and rotate the camera) */ 2 | 3 | #impressionist-cameracontrols { 4 | pointer-events: auto; 5 | } 6 | 7 | #impressionist-cameracontrols-xy { 8 | position: fixed; 9 | bottom: 50px; 10 | left: 50px; 11 | } 12 | #impressionist-cameracontrols-z { 13 | position: fixed; 14 | top: 50px; 15 | right: 50px; 16 | } 17 | #impressionist-cameracontrols-rotate { 18 | position: fixed; 19 | bottom: 50px; 20 | right: 50px; 21 | } 22 | -------------------------------------------------------------------------------- /src/plugins/cameracontrols/cameracontrols.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Camera-controls plugin 3 | * 4 | * Buttons to navigate the camera 5 | * 6 | * Copyright 2016 Henrik Ingo (@henrikingo) 7 | * Released under the MIT license. 8 | */ 9 | (function ( document, window ) { 10 | 'use strict'; 11 | var cameraControls; 12 | var coordWidgets = {}; 13 | var myWidgets = {}; 14 | var rotationAxisLock = {x:false, y:false, z:false}; 15 | var util = impressionist().util; 16 | var gc = impressionist().gc; 17 | 18 | // Functions for zooming and panning the canvas ////////////////////////////////////////////// 19 | 20 | // Create widgets and add them to the cameracontrols div ////////////////////////////////// 21 | var round = function(coord) { 22 | var keys = ["x", "y", "z", "rotateX", "rotateY", "rotateZ"]; 23 | for (var i in keys ) { 24 | coord[keys[i]] = Math.round( coord[ keys[i] ] ); 25 | } 26 | return coord; 27 | }; 28 | 29 | // Get "camera" or "step" depending on which is the active tab. 30 | // If neither is active, return the first tab ("camera"). 31 | var getActiveTab = function() { 32 | var selectedTab = document.querySelector("#impressionist-toolbar-titles button.selected"); 33 | if (selectedTab.innerHTML.toLowerCase() == "step") { 34 | return "step"; 35 | } 36 | return "camera"; 37 | }; 38 | // Return an order to use, depending on which of camera or step tab is active 39 | var getActiveOrder = function() { 40 | var activeTab = getActiveTab(); 41 | if (activeTab) { 42 | return coordWidgets[activeTab]["order"].input.value; 43 | } 44 | return coordWidgets["camera"]["order"].input.value; 45 | }; 46 | 47 | // This function actually creates and adds the controls 48 | var addCameraControls = function() { 49 | myWidgets.xy = util.makeDomElement( '' ); 50 | myWidgets.z = util.makeDomElement( '' ); 51 | myWidgets.rotateXY = util.makeDomElement( '' ); 52 | 53 | cameraControls = util.makeDomElement( '' ); 54 | cameraControls.appendChild(myWidgets.xy); 55 | cameraControls.appendChild(myWidgets.z); 56 | cameraControls.appendChild(myWidgets.rotateXY); 57 | gc.appendChild(document.body, cameraControls); 58 | 59 | var initDrag = function(event) { 60 | var drag = {}; 61 | drag.start = {}; 62 | drag.start.x = event.clientX; 63 | drag.start.y = event.clientY; 64 | drag.current = {}; 65 | drag.current.x = event.clientX; 66 | drag.current.y = event.clientY; 67 | return drag; 68 | }; 69 | var stopDrag = function() { 70 | myWidgets.xy.drag = false; 71 | myWidgets.z.drag = false; 72 | myWidgets.rotateXY.drag = false; 73 | }; 74 | 75 | myWidgets.xy.addEventListener( "mousedown", function( event ) { 76 | myWidgets.xy.drag = initDrag(event); 77 | updateCameraCoordinatesFiber(); // start fiber 78 | }); 79 | myWidgets.z.addEventListener( "mousedown", function( event ) { 80 | myWidgets.z.drag = initDrag(event); 81 | updateCameraCoordinatesFiber(); // start fiber 82 | }); 83 | myWidgets.rotateXY.addEventListener( "mousedown", function( event ) { 84 | myWidgets.rotateXY.drag = initDrag(event); 85 | updateCameraCoordinatesFiber(); // start fiber 86 | }); 87 | 88 | gc.addEventListener( document, "mouseup", function( event ) { 89 | stopDrag(); 90 | }); 91 | gc.addEventListener( document, "mouseleave", function( event ) { 92 | stopDrag(); 93 | }); 94 | 95 | gc.addEventListener( document, "mousemove", function( event ) { 96 | if( myWidgets.xy.drag ) { 97 | myWidgets.xy.drag.current.x = event.clientX; 98 | myWidgets.xy.drag.current.y = event.clientY; 99 | } 100 | if( myWidgets.z.drag ) { 101 | myWidgets.z.drag.current.x = event.clientX; 102 | myWidgets.z.drag.current.y = event.clientY; 103 | } 104 | if( myWidgets.rotateXY.drag ) { 105 | myWidgets.rotateXY.drag.current.x = event.clientX; 106 | myWidgets.rotateXY.drag.current.y = event.clientY; 107 | } 108 | }); 109 | 110 | var updateCameraCoordinatesFiber = function(){ 111 | var activeWidgets = coordWidgets[getActiveTab()]; 112 | 113 | // Now we're all set to calculate the actual diff caused by dragging one of the controls 114 | var diff = { x:0, y:0, z:0, rotateX:0, rotateY:0, rotateZ:0 }; 115 | diff.order = getActiveOrder(); 116 | var isDragging = false; 117 | if( myWidgets.xy.drag ) { 118 | diff.x = myWidgets.xy.drag.current.x - myWidgets.xy.drag.start.x; 119 | diff.y = myWidgets.xy.drag.current.y - myWidgets.xy.drag.start.y; 120 | isDragging = true; 121 | } 122 | if( myWidgets.z.drag ) { 123 | diff.z = myWidgets.z.drag.current.y - myWidgets.z.drag.start.y; 124 | diff.rotateZ = myWidgets.z.drag.current.x - myWidgets.z.drag.start.x; 125 | isDragging = true; 126 | } 127 | if( myWidgets.rotateXY.drag ) { 128 | diff.rotateX = myWidgets.rotateXY.drag.current.y - myWidgets.rotateXY.drag.start.y; 129 | diff.rotateY = myWidgets.rotateXY.drag.current.x - myWidgets.rotateXY.drag.start.x; 130 | isDragging = true; 131 | } 132 | 133 | if( isDragging ) { 134 | diff = snapToGrid(diff); 135 | diff = coordinateTransformation(diff); 136 | var moveTo = {}; 137 | var scale = util.toNumber(activeWidgets.scale.input.value, 1); 138 | moveTo.x = Number(activeWidgets.x.input.value) + diff.x * scale; 139 | moveTo.y = Number(activeWidgets.y.input.value) + diff.y * scale; 140 | moveTo.z = Number(activeWidgets.z.input.value) + diff.z * scale; 141 | moveTo.scale = scale + util.toNumber(diff.scale); 142 | moveTo.rotateX = Number(activeWidgets.rotateX.input.value) - diff.rotateX/10; 143 | moveTo.rotateY = Number(activeWidgets.rotateY.input.value) + diff.rotateY/10; 144 | moveTo.rotateZ = Number(activeWidgets.rotateZ.input.value) - diff.rotateZ/10; 145 | moveTo.order = diff.order; // Order is not a diff, just set the new value 146 | moveTo = round(moveTo); 147 | util.triggerEvent(document, "impressionist:camera:setCoordinates", moveTo ); 148 | setTimeout( updateCameraCoordinatesFiber, 100 ); 149 | } 150 | }; 151 | 152 | // Ignore small values in diff values. 153 | // For example, if the movement is 88 degrees in some direction, this should correct it to 154 | // 90 degrees. Helper for updateCameraCoordinatesFiber(). 155 | var snapToGrid = function(diff) { 156 | // To start, simply ignore any values < 5 pixels. 157 | // This creates 158 | // - a 10x10 px square whithin which there won't be any movement 159 | // - outside of that, 10 px corridoors in each 90 degree direction, 160 | // within which small deviations from 90 degree angles are ignored. 161 | for( var k in diff ) { 162 | if ( k == "order" ) continue; 163 | diff[k] = Math.abs(diff[k]) > 5 ? diff[k] : 0; 164 | } 165 | // For the z and o widgets, attach to full 90 degrees in the closest direction. 166 | // This means you can only zoom or rotate in one direction, not both at the same time. 167 | // Once a direction is chosen, lock that until dragStop() event. 168 | if( myWidgets.z.drag && myWidgets.z.drag.setzero ) { 169 | diff[myWidgets.z.drag.setzero] = 0; 170 | } 171 | else { 172 | if( Math.abs(diff.z) > Math.abs(diff.rotateZ) ) { 173 | diff.rotateZ = 0; 174 | myWidgets.z.drag.setzero = "rotateZ"; 175 | } 176 | else if ( Math.abs(diff.z) < Math.abs(diff.rotateZ) ) { 177 | diff.z = 0; 178 | myWidgets.z.drag.setzero = "z"; 179 | } 180 | } 181 | if( myWidgets.rotateXY.drag && myWidgets.rotateXY.drag.setzero ) { 182 | diff[myWidgets.rotateXY.drag.setzero] = 0; 183 | } 184 | else { 185 | if( Math.abs(diff.rotateX) > Math.abs(diff.rotateY) ) { 186 | diff.rotateY = 0; 187 | myWidgets.rotateXY.drag.setzero = "rotateY"; 188 | } 189 | else if ( Math.abs(diff.rotateX) < Math.abs(diff.rotateY) ) { 190 | diff.rotateX = 0; 191 | myWidgets.rotateXY.drag.setzero = "rotateX"; 192 | } 193 | } 194 | return diff; 195 | }; 196 | }; 197 | 198 | // Reset rotationAxisLock when entering a new step, or when order field was manually edited 199 | var resetRotationAxisLock = function () { 200 | rotationAxisLock = {x:false, y:false, z:false}; 201 | }; 202 | 203 | // Reset rotationAxisLock whenever entering a new step 204 | gc.addEventListener(document, "impress:stepenter", function (event) { 205 | resetRotationAxisLock(); 206 | }); 207 | 208 | // Wait for camera plugin to initialize first 209 | 210 | gc.addEventListener(document, "impressionist:camera:init", function (event) { 211 | coordWidgets["camera"] = event.detail.widgets; 212 | // Reset rotationAxisLock if the order field was manually edited 213 | coordWidgets["camera"].order.input.addEventListener("input", function (event) { 214 | resetRotationAxisLock(); 215 | }); 216 | coordWidgets["camera"].order.plus.addEventListener("click", function (event) { 217 | resetRotationAxisLock(); 218 | }); 219 | coordWidgets["camera"].order.minus.addEventListener("click", function (event) { 220 | resetRotationAxisLock(); 221 | }); 222 | 223 | addCameraControls(); 224 | impressionist().util.triggerEvent( cameraControls, "impressionist:cameracontrols:init" ); 225 | }); 226 | 227 | gc.addEventListener(document, "impressionist:stepmove:init", function (event) { 228 | coordWidgets["step"] = event.detail.widgets; 229 | // Reset rotationAxisLock if the order field was manually edited 230 | coordWidgets["step"].order.input.addEventListener("input", function (event) { 231 | resetRotationAxisLock(); 232 | }); 233 | coordWidgets["step"].order.plus.addEventListener("click", function (event) { 234 | resetRotationAxisLock(); 235 | }); 236 | coordWidgets["step"].order.minus.addEventListener("click", function (event) { 237 | resetRotationAxisLock(); 238 | }); 239 | }); 240 | 241 | // 3d coordinate transformations 242 | // 243 | // Without this, the controls work, but they will just modify the camera 244 | // coordinates directly. If the camera was rotated, this no longer makes 245 | // sense. For example, setting rotate: z: to 180, would turn everything 246 | // upside down. Now, if you pull the "+" (xy) control up, you will 247 | // actually see the camera panning down. 248 | // 249 | // We want the controls to move the camera relative to the current viewport/camera position, 250 | // not the origin of the xyz coordinates. These functions modify the diff object so that 251 | // the movements are according to current viewport. 252 | // 253 | // For the x/y/z translations, we simply modify the diff vector to account for all the possible 254 | // rotations that might be in place. 255 | // 256 | // Based on http://www.math.tau.ac.il/~dcor/Graphics/cg-slides/geom3d.pdf 257 | // and https://24ways.org/2010/intro-to-css-3d-transforms/ 258 | // 259 | // For adjusting rotations, a different strategy is needed. 260 | // It turns out that for rotations order matters, and whatever is the first rotation, will 261 | // just work without modification. For the following ones, we could try some sin()*cos() 262 | // multiplication magic, but in some edge cases (in particular, rotateY(90) with the default 263 | // order=xyz) two axes can collapse into one, so we lose a dimension and no amount of sin()*cos() 264 | // is able to do anything about that. So instead with rotations the strategy is just to move 265 | // the axis currently being rotated to be last. This is trivial if the current rotation around 266 | // that axis is 0. If it is non-zero, we can not do anything, but leave the order as it is 267 | // and just rotate anyway. This can often look odd to the user. Sorry. 268 | var coordinateTransformation = function(diff){ 269 | var deg = function(rad) { 270 | return rad * (180 / Math.PI); 271 | }; 272 | 273 | var rad = function(deg) { 274 | return deg * (Math.PI / 180); 275 | }; 276 | 277 | var newDiff = {}; 278 | 279 | var xyz = diff.order; // Note: This is the old value, not a diff. The only place to change it is this method. 280 | 281 | var cameraWidgets = coordWidgets["camera"]; 282 | var angle = { 283 | x: util.toNumber(cameraWidgets.rotateX.input.value), 284 | y: util.toNumber(cameraWidgets.rotateY.input.value), 285 | z: util.toNumber(cameraWidgets.rotateZ.input.value) 286 | }; 287 | 288 | var computeRotate = { 289 | x: function(angle, v){ 290 | var vv = []; 291 | vv[0] = v[0]; 292 | vv[1] = v[1] * Math.cos( rad(angle) ) - v[2] * Math.sin( rad(angle) ); 293 | vv[2] = v[2] * Math.cos( rad(angle) ) + v[1] * Math.sin( rad(angle) ); 294 | return vv; 295 | }, 296 | y: function(angle, v){ 297 | var vv = []; 298 | vv[0] = v[0] * Math.cos( rad(angle) ) + v[2] * Math.sin( rad(angle) ); 299 | vv[1] = v[1]; 300 | vv[2] = v[2] * Math.cos( rad(angle) ) - v[0] * Math.sin( rad(angle) ); 301 | return vv; 302 | }, 303 | z: function(angle, v){ 304 | var vv = []; 305 | vv[0] = v[0] * Math.cos( rad(angle) ) - v[1] * Math.sin( rad(angle) ); 306 | vv[1] = v[1] * Math.cos( rad(angle) ) + v[0] * Math.sin( rad(angle) ); 307 | vv[2] = v[2]; 308 | return vv; 309 | } 310 | }; 311 | 312 | // Transform the [x, y, z] translation vector moving the camera to account for the current rotations. 313 | // Note that for the camera. aka the canvas, impress.js applies the rotation in the reverse order 314 | var v = [ diff.x, diff.y, diff.z ]; 315 | for ( var i = xyz.length-1; i >= 0; i-- ) { 316 | v = computeRotate[xyz[i]](angle[xyz[i]], v); 317 | } 318 | newDiff.x = v[0]; 319 | newDiff.y = v[1]; 320 | newDiff.z = v[2]; 321 | 322 | // Rotations 323 | // Capture current rotations from activeWidgets 324 | var activeWidgets = coordWidgets[getActiveTab()]; 325 | var currentRotations = {}; 326 | for ( var i = 0; i < xyz.length; i++ ) { 327 | // iterate over rotateX/Y/Z in the order they appear in "order" 328 | var rotateStr = "rotate" + xyz[i].toUpperCase(); 329 | currentRotations[xyz[i]] = util.toNumber( activeWidgets[rotateStr].input.value ); 330 | } 331 | 332 | // Controls only allow 1 axis at a time to be rotating. Find out which one, if any. 333 | var axis = ""; 334 | if ( diff.rotateX ) axis = "x"; 335 | if ( diff.rotateY ) axis = "y"; 336 | if ( diff.rotateZ ) axis = "z"; 337 | 338 | // See if we can move that axis last in the order field 339 | if ( Math.abs( currentRotations[axis] ) < 1 && !rotationAxisLock[axis] ) { 340 | // Move that axis last in the order 341 | newDiff.order = diff.order.split(axis).join("") + axis; 342 | } 343 | // However, we only ever move the axis once. Changing the axis (direction) of rotation 344 | // once it has started is confusing to the user. So now that we're moving along this axis, 345 | // it cannot be moved in the order anymore. 346 | rotationAxisLock[axis] = true; 347 | 348 | newDiff.rotateX = diff.rotateX; 349 | newDiff.rotateY = diff.rotateY; 350 | newDiff.rotateZ = diff.rotateZ; 351 | newDiff.scale = diff.scale; 352 | return newDiff; 353 | }; 354 | 355 | })(document, window); 356 | 357 | -------------------------------------------------------------------------------- /src/plugins/electron/electron.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Electron IPC 3 | * 4 | * This is the renderer side of some Electron IPC calls. 5 | * 6 | * Copyright 2016 Henrik Ingo (@henrikingo) 7 | * Released under the MIT license. 8 | */ 9 | (function ( document, window ) { 10 | 'use strict'; 11 | 12 | // Return the entire document when requested 13 | if( window.require ){ 14 | var getDocumentElement = function (event, filename) { 15 | // Remove DOM elements added by impressionist itself (toolbars, tinymce) 16 | const rootDir = impressionist().rootDir 17 | impressionist().gc.removeAll(); 18 | impress().tear(); 19 | trimLastChild(); 20 | 21 | ipc.send('impressionist-return-documentElement', { 22 | filename: filename, 23 | documentElement: document.documentElement.outerHTML 24 | }); 25 | 26 | // Aaannd then we reload impress and put the impressionist bits right back to where they were 27 | impress().init(); 28 | var script = impressionist().util.loadJavaScript( 29 | impressionist().rootDir + "/js/impressionist.js", function(){ 30 | impressionist().rootDir = rootDir; 31 | impressionist().gc.pushElement(script); // The circle of life :-) 32 | impressionist().util.triggerEvent(document, "impressionist:init", {}) 33 | }); 34 | }; 35 | var ipc = require('electron').ipcRenderer; 36 | // Each call to getDocumentElement will end with re-adding impressionist.js script element 37 | // Thus causing a fresh copy of getDocumentElement itself to be registered as listener 38 | ipc.once('impressionist-get-documentElement', getDocumentElement); 39 | } 40 | 41 | /** 42 | * Trim Newlines from the lastChild of body 43 | * 44 | * For reasons I don't know, Google Chrome, and therefore Electron as well, adds 1-2 newlines 45 | * to the end of the body of any html page it opens. You can see this by simply typing in the 46 | * javascript console of a simple test page: 47 | * 48 | * document.documentElement.innerHTML.toString() 49 | * 50 | * or 51 | * 52 | * document.body.lastChild.nodeValue 53 | * 54 | * This is a bit annoying if we're gonna open and save a html document in Electron. For each time 55 | * we'd open and save a particular file, it would add newlines to the end of itself, causing the 56 | * file to grow indefinitively. 57 | * 58 | * To avoid this, we trim extra newlines from the end of the file. 59 | */ 60 | var trimLastChild = function() { 61 | while ( true ) { 62 | var end = document.body.lastChild; 63 | if (end.nodeType != 3) break; 64 | end = end.nodeValue; 65 | if ( end.slice(-3) != "\n\n\n" ) break; 66 | // Trim one newline from the end 67 | document.body.lastChild.nodeValue = end.slice(0,-1); 68 | } 69 | }; 70 | 71 | })(document, window); 72 | -------------------------------------------------------------------------------- /src/plugins/loadcss/loadcss.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Load impressionist.css 3 | * 4 | * Copyright 2016 Henrik Ingo (@henrikingo) 5 | * Released under the MIT license. 6 | */ 7 | (function ( document, window ) { 8 | 'use strict'; 9 | impressionist().gc.addEventListener(document, "impressionist:init", function (event) { 10 | var link = impressionist().util.loadCss(impressionist().rootDir + "/css/impressionist.css"); 11 | impressionist().gc.pushElement(link); 12 | }); 13 | 14 | })(document, window); 15 | -------------------------------------------------------------------------------- /src/plugins/stepedit/stepedit.css: -------------------------------------------------------------------------------- 1 | /* Stepedit plugin. Reorder steps dialog. */ 2 | 3 | #impressionist-stepedit-reorder-dialog { 4 | pointer-events: auto; 5 | background-color: #fff; 6 | border-bottom: solid 1px #ddd; 7 | border-right: solid 1px #ddd; 8 | opacity: 0.9; 9 | color: #000; 10 | padding: 10px; 11 | width: 200px; 12 | } 13 | 14 | #impressionist-stepedit-reorder-dialog table { 15 | width: 100%; 16 | } 17 | 18 | #impressionist-stepedit-reorder-dialog td { 19 | vertical-align: middle; 20 | } 21 | 22 | #impressionist-stepedit-reorder-dialog button { 23 | width: 22px; 24 | height: 22px; 25 | } -------------------------------------------------------------------------------- /src/plugins/stepedit/stepedit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Stepedit plugin 3 | * 4 | * Add, remove and reorder steps 5 | * 6 | * Copyright 2017 Henrik Ingo (@henrikingo) 7 | * Released under the MIT license. 8 | */ 9 | (function ( document, window ) { 10 | 'use strict'; 11 | var root; 12 | var canvas; 13 | var toolbar; 14 | var steps = []; 15 | var activeStep; 16 | var waitForRefresh = false; 17 | var reorderDialog = null; 18 | var reorderSelect = null; 19 | var reorderOldSelectedOptions = []; 20 | var group = 0; 21 | var myWidgets = {}; 22 | var cameraWidgets = {}; 23 | var cameraWidgetNames = [['x', 'data-x'], 24 | ['y', 'data-y'], 25 | ['z', 'data-z'], 26 | ['scale', 'data-scale'], 27 | ['rotateX', 'data-rotate-x'], 28 | ['rotateY', 'data-rotate-y'], 29 | ['rotateZ', 'data-rotate-z'], 30 | ['order', 'data-rotate-order']]; 31 | var util = impressionist().util; 32 | 33 | 34 | var createWidgets = function() { 35 | // Set the text of the tab for this group of widgets 36 | util.triggerEvent(toolbar, "impressionist:toolbar:groupTitle", { group: group, title: "Outline" } ) 37 | 38 | // Add button for new step 39 | var newButton = util.makeDomElement( '' ); 40 | util.triggerEvent(toolbar, "impressionist:toolbar:appendChild", { group: group, element: newButton }); 41 | myWidgets["new"] = newButton; 42 | newButton.addEventListener("click", function (event) { 43 | if ( !waitForRefresh ) { 44 | newStep(); 45 | } 46 | }); 47 | 48 | // Add button to delete step 49 | var deleteButton = util.makeDomElement( '' ); 50 | util.triggerEvent(toolbar, "impressionist:toolbar:appendChild", { group: group, element: deleteButton }); 51 | myWidgets["delete"] = deleteButton; 52 | deleteButton.addEventListener("click", function (event) { 53 | if ( !waitForRefresh ) { 54 | deleteStep(); 55 | } 56 | }); 57 | 58 | // Add empty space 59 | var space = util.makeDomElement( ' ' ) 60 | util.triggerEvent(toolbar, "impressionist:toolbar:appendChild", { group: group, element: space }); 61 | 62 | // Add button to reorder steps 63 | var reorderButton = util.makeDomElement( '' ); 64 | util.triggerEvent(toolbar, "impressionist:toolbar:appendChild", { group: group, element: reorderButton }); 65 | myWidgets["reorder"] = reorderButton; 66 | reorderButton.addEventListener("click", function (event) { 67 | if ( reorderDialog ) { 68 | hideReorderDialog(); 69 | } 70 | else { 71 | showReorderDialog(); 72 | } 73 | }); 74 | }; 75 | 76 | // Helper functions /////////////////////////////////////////////////////////////////////////// 77 | 78 | var refresh = function( nextStep, slow ) { 79 | if (nextStep === undefined) { 80 | if ( activeStep ) { 81 | nextStep = activeStep.id; 82 | } 83 | else { 84 | nextStep = 1; 85 | } 86 | } 87 | 88 | waitForRefresh = true; 89 | if ( slow ) { 90 | // Use whatever data-transition-duration is set in the presentation #impress div 91 | impress().goto(nextStep); 92 | } 93 | else { 94 | // Fast refresh (no zoom or translate or rotate happening) 95 | impress().goto(nextStep, 1); 96 | } 97 | }; 98 | 99 | // Get the step after this one. Returns undefined if this is already the last step. 100 | var getNextStep = function( thisStep ) { 101 | var i = getNextStepIndex(thisStep); 102 | if ( i == 0 ) { 103 | return undefined; 104 | } 105 | else { 106 | return steps[i]; 107 | } 108 | }; 109 | 110 | // Get index for next step in steps array. If thisStep is already the last step, returns 0. 111 | var getNextStepIndex = function( thisStep ) { 112 | for( var i = 0; i < steps.length; i++ ) { 113 | if ( steps[i] == thisStep ) { 114 | if ( i == steps.length - 1 ) { 115 | return 0; 116 | } 117 | else { 118 | return i+1; 119 | } 120 | } 121 | } 122 | }; 123 | 124 | // Get the step before this one. Returns undefined if this is already the first step. 125 | var getPrevStep = function( thisStep ) { 126 | var i = getPrevStepIndex(thisStep); 127 | if ( i == -1 ) { 128 | return undefined; 129 | } 130 | else { 131 | return steps[i]; 132 | } 133 | }; 134 | 135 | // Get index for previous step in steps array. If thisStep is already the first step, returns -1. 136 | var getPrevStepIndex = function( thisStep ) { 137 | for( var i = steps.length; i >= 0; i-- ) { 138 | if ( steps[i] == thisStep ) { 139 | return i-1; 140 | } 141 | } 142 | }; 143 | 144 | // Generate a new step.id of the form "step-N", where N is one larger than the currently largest N 145 | var generateStepId = function() { 146 | var highest = 0; 147 | var re = /step-(\d+)/; 148 | steps.forEach( function(step) { 149 | var res = step.id.match(re); 150 | if (res) { 151 | var num = parseInt(res[1]); 152 | if ( num > highest ) highest = num; 153 | } 154 | }); 155 | var newId = highest + 1; 156 | return "step-" + newId; 157 | }; 158 | 159 | // Add some unique content to the new step, purely as a visual aid and placeholder for user 160 | // 161 | // :param: totalNum is what steps.length will be after the addition/deletion is complete. 162 | // We cannot read steps.length at the moment this function is called, because it is out of date but not yet updated. 163 | var generateStepContent = function(totalNum) { 164 | var currentNum = getNextStepIndex(activeStep) + 1; 165 | return "Step " + currentNum + " of " + totalNum + "
"; 166 | }; 167 | 168 | // Add, Delete etc... actions ///////////////////////////////////////////////////////////////// 169 | 170 | // Add new step after this one. 171 | // Copy all attributes of current step (including CSS classes, etc...). For position, copy camera coordinates. 172 | var newStep = function() { 173 | var newStepElement = document.createElement("DIV"); 174 | // Copy attributes of current step 175 | for (var i = 0; i < activeStep.attributes.length; i++) { 176 | var attrName = activeStep.attributes[i].name; 177 | newStepElement.setAttribute( attrName, activeStep.getAttribute(attrName) ); 178 | } 179 | // Copy coordinates from camera 180 | cameraWidgetNames.forEach( function(namePair){ 181 | newStepElement.setAttribute(namePair[1], cameraWidgets[namePair[0]].input.value); 182 | }); 183 | newStepElement.id = generateStepId(); 184 | newStepElement.innerHTML = generateStepContent(steps.length+1); 185 | 186 | // Actually insert the element 187 | var nextElement = getNextStep(activeStep); 188 | if ( nextElement ) { 189 | canvas.insertBefore( newStepElement, nextElement ); 190 | } 191 | else { 192 | canvas.appendChild( newStepElement ); 193 | } 194 | refresh(newStepElement.id); 195 | // Let others know that there's a new step 196 | util.triggerEvent( newStepElement, "impressionist:stepedit:newStep", {} ); 197 | }; 198 | 199 | // Delete step 200 | var deleteStep = function() { 201 | // There cannot be zero steps. If this is the last step, just empty it. 202 | if ( steps.length == 1 ) { 203 | activeStep.innerHTML = generateStepContent(steps.length); 204 | return; 205 | } 206 | // After deleting, we want to land on the following step 207 | // Minus one, because we're deleting this slide 208 | var nextIndex = getNextStepIndex(activeStep)-1; 209 | // Now delete 210 | activeStep.parentElement.removeChild(activeStep); 211 | 212 | if (nextIndex >= 0) { 213 | refresh(nextIndex, true); 214 | } 215 | else { 216 | // If we were already on the last step, we don't wrap around to first step, rather just stay on the last 217 | refresh(steps.length-2, true); 218 | } 219 | }; 220 | 221 | 222 | // Reorder dialog 223 | var showReorderDialog = function() { 224 | reorderOldSelectedOptions = []; 225 | 226 | var size = steps.length < 16 ? steps.length : 16; 227 | reorderDialog = util.makeDomElement( 'Reorder steps
\n' + 229 | '\n' + 231 | ' \n' + 233 | ' | \n' +
234 | ' \n' + 235 | ' \n' + 236 | ' |