diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000..48d5f81 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..df81be2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.gitignore b/.gitignore index ce38f27..e0e9510 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -_ext +/_ext/* +!/_ext/svg/ routes build node_modules diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..3f5fc21 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v15.14.0 \ No newline at end of file diff --git a/Model.drawio b/Model.drawio new file mode 100644 index 0000000..629b961 --- /dev/null +++ b/Model.drawio @@ -0,0 +1 @@ +7V1bc+K4Ev41VOU8kPI98BjIZGb3MHuyk9nM7KOCBWhiWxxZBJhfv5ItG2zJxgSMsx5XpSq4Jd/0tVqfultyzxz7m48ELBefsQu9nqG5m5551zMMfWAN2D8u2QqJMxSSOUGukO0Ej+gnFEJNSFfIhWGmIsXYo2iZFU5xEMApzcgAIXidrTbDXvauSzCHkuBxCjxZ+g25dBFLB8bNTv4JovkiuTN7wbjEB0ll8SbhArh4vScyP/TMMcGYxr/8zRh6vPWSdvn22/abN3lxPv7+Z/h/8Nfov1//eOrHF7s/5pT0FQgM6Jsv3X+6tz5PHp5mqz/+fnn83+Oy/xX3LUe8G90mDQZd1n7iEBO6wHMcAO/DTjoieBW4kF9WY0e7OhOMl0yoM+EPSOlWKANYUcxEC+p7opS9Btl+Zwd97VpLBX/zC15rw1RwtxG3iI+2+0cPkCAfUkiEMH4N/uw52A+0magX4hWZilN/vszuPz1ZP74vH+7/XGsfb59/74u21Sggc0hLGnRPM1ifgpg9I9myEwn0AEWv2acDQrfnab0dfuyHgFANZ9lTvgJvJe70lfW18DMMVhLO4Rr5Hgg4oDMc0ARy9vAj4KF5wH5PWdvxFh69QkIR61O3ooByoEfTBfLcCdjiFW+RkILpS3I0WmCCfrLLggRzVkyo0AjDydR45GcKHAkMWZ2HBDY9FU1ASEWdKfY8sAzRc/TAvIrPYEHBCFOK/eRCGSVN+250QAl+Sa2BnmpPVWXhrQE3e0jKcItSJ7GCifU0xfF6Z4p0R8gW+2bI0opVRNzuCzOXIJizRkjvZ5i5+1kV72c42dsBjwEfAApHvBlDSTHTV327rpqSrvbF2BBKusqam+7ppQdntFArwyWYomA+iercWTvJF/G6XITZuTMv0ogFcl0YRBpDAQWxUnE1WWIU0Kg97BH7Y6021q7tns0eaMyO9d0x++PVCR3jgCkXQJEWQaaxa8i1tkwZhVnUlFpY2skPa+E2C+6xSnA2s2RJUEsIeyiyRHuDhNxLD8DvMyCj8Ung/TUajfq6pBOmrBOmAn8PPEPvAYeIIsyvT+K6Ob04BH1doNpGNVAHNWFqKzAdcUyhx+wSH3aukPufHtcpU4+1mxczLpir0LKuXhfeTkVLPigZOKoCrqaKg0apIm//faJ4gCSekQ8qW6MqH9SdC/FB5d1tu1HMctTesP8loJlmo6DdNAHae2l7Y9hk2zuNzIffS9s7dpNtL09eH9AGspZGFBMJlm76mp++SlyiqgYVTl9T11fJ9NVQziTMEtr5r5u+qgd2SVv59NXD83CP8/YjQkwpo6R58ZLpBaRhhiBnKsyQB/MngSnvhveIN1rZebHDJyOmO0dQ9jmAxx5QdYYHtpCoTnERYO+5go/bkEK/+Elipg9dRpUpV8O2Uf1jZvUHOmexQTxmVq/ui5pVk8E2pC4gQdzyaX0NqFY1sWeY16vZpwziLzzNsyoyJ91okjnJ/rV4iOgo08UpUz/lLMe6/NOIaXs5k+wz7Ee9lxN8HnrO8QcU3kZ8p5hhxBSluDzmSxNeS8lN2AigKnydbVTi6QJOXyB5xoC4orxjNEeNfdaxjEYdHDPrYjROx2jOjqpe2XNdE6g3ClB5KAIHkXlhFu9KjlPggLXNXnHLOnp9YA+qgW0aNaE9kND+8MreaLSSo8odH6qfDzk5OmQZFa2BrrWeDg2VdIgpmffM1OHXzoJ4i2kanIVdnCMLQj1HlB3cHbs4FdULpkGoQZX9wAmBuIKvkddzvNepZaIBfUTjmh3LqAp6VUZZlkR3Guqy61M9IewoxiVcLo59kGOk3pWMqW9VlqVaU9VplgHwJW+Le9j9EoufUKwWefkET19g/irTJPhT5KNxCVgzMzUGwStQBMleEVwnZS0zj3XzoSKndEkASdVL6nO36F1iaA24qhwuSmBr40RFuaEUz5khjc0HoluF2yWuERsSRfFq6TJr+bBvFXK1WmYialQS2VGjVJKbuhw1tiWhddHcxGtNy4Yar7Ubo9fs0qOSFNJ3H4TUZed5xIm7xUdNZW+92fV20/7sLTkooIwnciG3Hh886HcpTG8YhQrS34/0ydXHQOVwQcdAT4a16YifLnvWY/4I3Dhwr6CWBPpYpAqoFy6tlh6DlJZU8SEboEeQ9V5l8YyNwmwqfOt5ituLQjG1Vj4fn7OnT98yO1SfKlaMR+pl2TenZUmqF2V3lKgBSjSwsxSlqqHSh613FBpyIKPIURg+isTuNs+862ZGRRtFHMmMrNqSu+UQR8eMTob1pmFmZMjhALFsmw8HKo9bqCIjIeu3qsooJ5SSqmLLUZBvFaaFLbMl9alTZXZTlxfPkF34Xzg/4A3Z0ZyGaI6uDd/Ic8z28xx1Evp0RXiTR5rSNvNTO5U5T1p3bU4eQ/ZMd1TmZFibdvIYRXndKJgS1gCQ92UF03BhaXEIIyNwxY1/x0Sqa0NFJuLURkRkn9/D/tLmjoZcnIbktw/op5sT7WvJQMULjNbzkORR8zwEe7iLQJ2TnBTsJ1NCTlQaWZufxVQlEHfk5ERYVX4WFax1kZNkeZMqAjWOuvhV3NOLAlEllXL1uXIA8pEAF7Emi8/QRdY5/20oOA6DZLSdrHwUcARViVhxlTtAXgIY5pOsdhUeAV0RwHWgsMoY+0scxI/mLxW1fD40YtI5fo7RbwXdUum3btXFt0zZk3if7jzTka2Lk61+nmylC+gO6kj7V9qZqozUbpA9ZISK9kE8kjvVNsiq/Dp8SAngmtsiVZhhCYOCohC8wgeCf6jjE3DDm3ivvBupqilJ4tU7pCSOU5OW2LoE1q+8B6op3GYHs4mtgvHmMtnEpuzd+4RCisWlO3ZxYXZh5iJK1b9k0P5tjUw5iTTaiJEAH3ar1o4eT26OJh1KPajNYaNKLu245Imw2hW7d11c0pLdsSKnJXCxMh1XKeYkUoxTHUk8Av2K0aNkt4fzwy97YQWM9yTKgOw4x+U5h2O/kXO0P1s3ceup1i/dse6v2idRbMOcDy5No2W0XeLLW/ZCO893l+y6qIqlyvbsqMqJsKqC2BelKs181GWD6Pe937E7wxZHO2cGP8gsl/6+fxCfpVvmAS9IMwute5JnpPjbTU05RqxGvg5T9sVOXXPeI5pJqvvhL84UZDZdCM9ho3jqDE4ni6c2OOSjfC+985gNSi/07SatUTS16+FwkOud9rvc1MKuiGbRxPFCn4OSafZd7uMn3cQwObjYxNDOfVVXHepWOqPbn1foqDd8E+sbEu2V9Lab6pXPCWJLcLpXuq4op9NtpVYDrE3vLusUfmV3gddJX77ia7RlVzTrdfBAlamHQ3jreUm1sPNZV9eNqutfnJIhp0A52CHBmO4PDAQsF/wTKbzGPw== \ No newline at end of file diff --git a/README.md b/README.md index bf2c1c9..2239b2c 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,14 @@ The next version is mostly focused on adding missing essential features and port Suggestions / Planned features: +- Documentation + +- Possibility to hide and resize menus (layers, palette) - Line tool - Tiled mode - Load palette from LPE file -- Move colours in (advanced) palette editor - Symmetry options -- Custom color picker - - custom code without dependencies - - more features such as sliders / color modes - - Mobile - Touch equivalent for mouse clicks - Hide or scale ui @@ -32,25 +30,29 @@ Suggestions / Planned features: - Possibly add collaborate function - Polish: - - ctrl + a to select everything / selection -> all, same for deselection - - Show colors which would need to be added to palette + - ctrl a to select everything / selection -> all, same for deselection - Warning windows for wrong inputs - Palette option remove unused colors - Move selection with arrows - - Update pivot buttons when resizing canvas - Update borders by dragging the canvas' edges with the mouse when resizing canvas - Move the canvases so they're centered after resizing the canvas (maybe a .center() method in layer class) - - Trim canvas + - Scale selection ## How to Contribute -Requirements: you must have node.js and git installed. +### Requirements + +You must have node.js and git installed. + +You also need `npm` in version 7 (because of 2nd version of lockfile which was introduced there) which comes with Node.js 15 or newer. To simplify installation of proper versions you can make use of [nvm](https://github.com/nvm-sh/nvm#installing-and-updating) and run `nvm install` – it will activate proper Node.js version in your current command prompt session. + +### Contribution Workflow 1. Click **Fork** above. It will automatically create a copy of this repository and add it to your account. 2. Clone the repository to your computer. -3. Open the folder in command prompt and run **npm install** +3. Open the folder in command prompt and run **`npm install`** 4. Make any changes you would like to suggest. -5. In command prompt run **node build.js** which will compile it to the */build* folder, where you can make sure it works +5. In command prompt run **`npm run hot`** which will compile app to the `/build` folder, serve under [http://localhost:3000](http://localhost:3000), then open in your browser. Moreover, it restarts server every time you save your changes in a codebase. You can go even further by running `npm run hot:reload`, which will also trigger webpage reloads. 6. Add, Commit and Push your changes to your fork. 7. On the github page for your fork, click **New Pull Request** above the file list. 8. Change the **head repository** dropdown to your fork. @@ -59,6 +61,12 @@ Requirements: you must have node.js and git installed. If you have any trouble, see this page: https://help.github.com/en/articles/creating-a-pull-request-from-a-fork +### Feature Toggles + +Some feature might be hidden by default. Functions to enable/disable them are available inside global `featureToggles` and operate on a `window.localStorage`. + +For example use `featureToggles.enableEllipseTool()` to make ellipse tool button visible. Then `featureToggles.disableEllipseTool()` to hide it. + ## License This software may not be resold, redistributed, rehosted or otherwise conveyed to a third party. diff --git a/_ext/scripts/utilities/getSetText.js b/_ext/scripts/utilities/getSetText.js index 4604c19..fbee0f9 100644 --- a/_ext/scripts/utilities/getSetText.js +++ b/_ext/scripts/utilities/getSetText.js @@ -7,4 +7,4 @@ function getText(elementId) { function setText(elementId, text) { var element = (typeof elementId == 'string' ? document.getElementById(elementId) : elementId); element.textContent = text; -} +} \ No newline at end of file diff --git a/_ext/svg/ellipse.svg b/_ext/svg/ellipse.svg new file mode 100644 index 0000000..e2f7391 --- /dev/null +++ b/_ext/svg/ellipse.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/_ext/svg/filledellipse.svg b/_ext/svg/filledellipse.svg new file mode 100644 index 0000000..803b053 --- /dev/null +++ b/_ext/svg/filledellipse.svg @@ -0,0 +1,22 @@ + + + Layer 1 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/_ext/svg/line.svg b/_ext/svg/line.svg new file mode 100644 index 0000000..f5aa17b --- /dev/null +++ b/_ext/svg/line.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_ext/svg/lospec_logo_1x.svg b/_ext/svg/lospec_logo_1x.svg new file mode 100644 index 0000000..d48ff76 --- /dev/null +++ b/_ext/svg/lospec_logo_1x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_ext/svg/newfile.svg b/_ext/svg/newfile.svg new file mode 100644 index 0000000..ba18c86 --- /dev/null +++ b/_ext/svg/newfile.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/_ext/svg/openfile.svg b/_ext/svg/openfile.svg new file mode 100644 index 0000000..e69f4af --- /dev/null +++ b/_ext/svg/openfile.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/build.js b/build.js index 933fbba..47444a8 100644 --- a/build.js +++ b/build.js @@ -15,8 +15,16 @@ console.log('Building Pixel Editor'); function copy_images(){ - gulp.src('./images/') - .pipe(gulp.dest(path.join(BUILDDIR, SLUG))); + // Icons + gulp.src('./images/*.png').pipe(gulp.dest(path.join(BUILDDIR, SLUG))); + // Splash images + gulp.src('./images/Splash images/*.png').pipe(gulp.dest(path.join(BUILDDIR, SLUG))); + // Logs images + gulp.src('./images/Logs/*.gif').pipe(gulp.dest(path.join(BUILDDIR, SLUG))); +} + +function copy_logs() { + gulp.src('logs/*.html').pipe(gulp.dest(path.join(BUILDDIR, SLUG))); } function render_js(){ @@ -56,4 +64,4 @@ function compile_page(){ // empty the build folder, or create it fs.rmdirSync(BUILDDIR, { recursive: true }); fs.mkdirSync(BUILDDIR); -gulp.parallel(copy_images, render_js, render_css, compile_page)(); +gulp.parallel(copy_images, copy_logs, render_js, render_css, compile_page)(); diff --git a/css/pixel-editor.scss b/css/pixel-editor.scss index 98d3b9e..305cccc 100644 --- a/css/pixel-editor.scss +++ b/css/pixel-editor.scss @@ -40,6 +40,33 @@ body { /* Disable Android and iOS callouts*/ } +#help { + max-height: 500px; + overflow-y:scroll; + + li { + margin-top:5px; + } + + // Fancy scrollbar + &::-webkit-scrollbar { + background: #232125; + width: 0.5em; + } + &::-webkit-scrollbar-track { + margin-top: -0.125em; + width: 0.5em; + } + &::-webkit-scrollbar-thumb { + background: #332f35; + border-radius: 0.25em; + border: solid 0.125em #232125; //same color as scrollbar back to fake padding + } + &::-webkit-scrollbar-corner { + background: #232125; + } +} + #layer-properties-menu { visibility: hidden; margin: 0; @@ -125,7 +152,7 @@ body { z-index: 1120; list-style-type: none; overflow-y:scroll; - overflow-x:hidden; // TODO: make the scroll bar a bit fancier + overflow-x:hidden; #add-layer-button { path { fill: $baseicon; @@ -178,7 +205,7 @@ body { .layers-menu-entry { cursor: pointer; - margin-top: 2px; + margin-bottom: 2px; font-size: 1em; color: $basetext; background-color: lighten($basecolor, 4%); @@ -301,7 +328,7 @@ svg { } #pixel-canvas { - z-index: 2; + z-index: 3; background: transparent; } @@ -311,7 +338,7 @@ svg { } #tmp-canvas { - z-index: 4; + z-index: 5; background: transparent; } @@ -354,7 +381,7 @@ svg { left: 64px; right: 48px; top: 48px; - cursor: none; + cursor: default; position: fixed; display: block; } @@ -687,14 +714,18 @@ svg { #tools-menu li button#pencil-bigger-button, #tools-menu li button#zoom-in-button, #tools-menu li button#eraser-bigger-button, -#tools-menu li button#rectangle-bigger-button { +#tools-menu li button#rectangle-bigger-button, +#tools-menu li button#ellipse-bigger-button, +#tools-menu li button#line-bigger-button { left: 0; } #tools-menu li button#pencil-smaller-button, #tools-menu li button#zoom-out-button, #tools-menu li button#eraser-smaller-button, -#tools-menu li button#rectangle-smaller-button { +#tools-menu li button#rectangle-smaller-button, +#tools-menu li button#ellipse-smaller-button, +#tools-menu li button#line-smaller-button { right: 0; } @@ -705,7 +736,11 @@ svg { #tools-menu li.selected button#eraser-bigger-button, #tools-menu li.selected button#eraser-smaller-button, #tools-menu li.selected button#rectangle-bigger-button, -#tools-menu li.selected button#rectangle-smaller-button { +#tools-menu li.selected button#rectangle-smaller-button, +#tools-menu li.selected button#ellipse-bigger-button, +#tools-menu li.selected button#ellipse-smaller-button, +#tools-menu li.selected button#line-bigger-button, +#tools-menu li.selected button#line-smaller-button { display: block; } @@ -775,8 +810,32 @@ svg { } } + div.update { + input { + background: $indent; + border: none; + border-radius: 4px; + color: $indenttext; + padding: 10px 20px; + margin: 0; + width: 60px; + text-align: center; + } + } + /* + input { + background: $indent; + border: none; + border-radius: 4px; + color: $indenttext; + padding: 10px 20px; + margin: 0; + width: 60px; + text-align: center; + } + */ + button.default { - float: right; background: $basehover; border: none; border-radius: 4px; @@ -789,17 +848,6 @@ svg { } } - input { - background: $indent; - border: none; - border-radius: 4px; - color: $indenttext; - padding: 10px 20px; - margin: 0; - width: 60px; - text-align: center; - } - .dropdown-button { background: $basehover url('/pixel-editor/dropdown-arrow.png') right center no-repeat; border: none; @@ -1038,9 +1086,9 @@ svg { input[type=number] { position:relative; margin-left:10px; - height:15px; - width:40px; - padding:8px; + height:15px !important; + width:40px !important; + padding:8px !important; } input[type=number]::-webkit-outer-spin-button, @@ -1212,4 +1260,602 @@ svg { background: $baseselected; } } +} +/***********************COLOUR PICKER*****************************/ +#colour-picker { + background-color:$basecolor; + width:250px; + height:350px; + position:absolute; + display:inline-block; + + input[type=text] { + background-color:$basetext; + color:$basecolor; + box-shadow:none; + border:none; + } + + input[type=range] { + width: 100%; + margin: 2.2px 0; + background-color: transparent; + -webkit-appearance: none; + } + input[type=range]::-webkit-slider-runnable-track { + background: #484d4d; + border: 0; + width: 100%; + height: 25.6px; + cursor: pointer; + } + input[type=range]::-webkit-slider-thumb { + margin-top: -2.2px; + width: 18px; + height: 30px; + background: $basetext; + border: 0; + cursor: pointer; + -webkit-appearance: none; + } + input[type=range]::-moz-range-track { + background: #484d4d; + border: 0; + width: 100%; + height: 25.6px; + cursor: pointer; + } + input[type=range]::-moz-range-thumb { + width: 18px; + height: 30px; + background: $basetextweak; + border: 0; + cursor: pointer; + } + + /*TODO: Use one of the selectors from https://stackoverflow.com/a/20541859/7077589 and figure out + how to remove the vertical space around the range input in IE*/ + @supports (-ms-ime-align:auto) { + /* Pre-Chromium Edge only styles, selector taken from hhttps://stackoverflow.com/a/32202953/7077589 */ + input[type=range].slider { + margin: 0; + /*Edge starts the margin from the thumb, not the track as other browsers do*/ + } + } +} + +#cp-modes { + margin: 0 0 0 0; + font-size:0; + height:40px; + float:left; + display:flex; + font-family: 'Roboto', sans-serif; + background-color:$basetextweak; + width:100%; + + button { + font-size:14px; + left:0; + right:0; + margin:0 0 0 0; + border: none; + border-radius: 0; + height:100%; + width:37x; + background-color:$basehover; + color:$basetext; + cursor:pointer; + } + + button:hover { + background-color:$baseicon; + color:$basetext; + } + + button.cp-selected-mode { + background-color:$baseicon; + color:$basetext; + } + + input { + width:60px; + } + + div { + background-color:yellow; + width:100%; + height:100%; + z-index:2; + position:relative; + } +} + +#sliders-container { + padding:10px; +} + +.cp-slider-entry { + width:100%; + height:30px; + display:flex; + align-items:center; + margin-top:2px; + position:relative; + + label { + width: 20px; + font-size:15px; + font-style: bold; + } + + input[type=text] { + text-align:center; + width: 30px; + overflow:visible; + margin-left:4px; + } +} + +.colour-picker-slider { + width:90%; +} + +#cp-minipicker { + width:100%; + height:100px; + position:relative; + margin: 0 0 0 0; + z-index: inherit 2000; + + input { + width:100%; + margin: none; + padding: none; + } + + .cp-colours-previews { + width:100%; + position:relative; + } + + .cp-colour-preview { + width:100%; + position:relative; + background-color:blue; + color:$basecolor; + float:left; + height:30px; + justify-content: center; + display:flex; + align-items: center; + font-size:12px; + } + + #cp-colour-picking-modes { + width:100%; + position:relative; + } + + button { + font-size:14px; + left:0; + right:0; + margin:0 0 0 0; + border: none; + border-radius: 0; + height:30px; + width:16.66666%; + float:left; + overflow:hidden; + background-color:$basehover; + color:$basetext; + cursor:pointer; + } + + button:hover { + background-color:$baseicon; + color:$basetext; + } + + button.cp-selected-mode { + background-color:$baseicon; + color:$basetext; + } +} + +#cp-canvas-container { + width:100%; + height:100%; + position:relative; +} + +#cp-spectrum { + width:100%; + height:100px; + position:absolute; + background-color: transparent; +} + +.cp-picker-icon{ + width:16px; + height:16px; + border-radius:100%; + position:absolute; + background-color:white; + z-index:2; + border:2px solid black; +} + + +/***************PALETTE BLOCK****************/ +div#palette-block { + z-index:1000; + position:relative; + resize: horizontal; + margin: 0 0 0 0; + width:600px; + height:400px; +} + +div#palette-container { + display:inline-block; + background-color: #232125; + position:absolute; + scrollbar-color: #332f35 #232125; + scroll-behavior: smooth; + left:300px; + width:300px; + height:320px; + overflow-y: scroll; + + &::-webkit-scrollbar { + background: #232125; + width: 0.5em; + } + &::-webkit-scrollbar-track { + margin-top: -0.125em; + width: 0.5em; + } + &::-webkit-scrollbar-thumb { + background: #332f35; + border-radius: 0.25em; + border: solid 0.125em #232125; //same color as scrollbar back to fake padding + } + &::-webkit-scrollbar-corner { + background: #232125; + } +} + +ul#palette-list { + list-style:none; + margin: 0 0 0 0; + padding: 0 0 0 0; + position:relative; + display:inline-block; + + li { + float:left; + width:50px; + height:50px; + border:none; + + min-width:20px; + min-height:20px; + max-width:75px; + max-height:75px; + } +} + + +div#pb-options { + position:relative; + left:280px; + height:30px; + width:300px; + top:300px; + + button { + border-radius:none; + position:relative; + float:left; + width:50%; + height:100%; + text-align:center; + cursor: pointer; + font-size:16px; + background-color:$baseicon; + border:none; + } + + button:hover { + color: $basehovertext; + background-color: $basehover; + } +} + +/********SPLASH PAGE*************/ +#splash { + width:100% !important; + height:100%!important; + + background-color: #232125 !important; + opacity: 1 !important; + + #splash-input { + width:74%; + height:100% !important; + color:$baselink; + + #splash-menu { + position:relative; + height:100%; + left:0; + top:0; + } + + #editor-logo { + font-weight:bold; + text-transform:uppercase; + font-size:20px; + height:35vh; + width:100%; + position:relative; + + background-image:url('https://cdn.discordapp.com/attachments/506277390050131978/795660870221955082/final.png'); + background-size:cover; + background-position:center; + background-repeat:no-repeat; + } + + #black { + width:100%; + height:100%; + position:relative; + background-color:rgba(0,0,0,0.2); + } + + #sp-coverdata { + padding:20px; + + p, a { + font-size:15px; + position:absolute; + text-transform:none; + right:20px; + } + + p { + top:0px; + } + + a { + font-size:17px; + bottom:20px; + text-decoration:underline; + } + } + } + + #sp-quickstart-container { + height:100%; + max-height: 500px; + width:70%; + float:right; + padding:40px; + + -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ + -moz-box-sizing: border-box; /* Firefox, other Gecko */ + box-sizing: border-box; /* Opera/IE 8+ */ + + overflow-y: scroll; + &::-webkit-scrollbar { + background: #232125; + width: 0.5em; + } + &::-webkit-scrollbar-track { + margin-top: -0.125em; + width: 0.5em; + } + &::-webkit-scrollbar-thumb { + background: #332f35; + border-radius: 0.25em; + border: solid 0.125em #232125; //same color as scrollbar back to fake padding + } + &::-webkit-scrollbar-corner { + background: #232125; + } + } + + #sp-quickstart { + display:flex; + flex-direction: row; + flex-wrap: wrap; + height:100%; + + // Fancy scrollbar + &::-webkit-scrollbar { + background: #232125; + width: 0.5em; + } + &::-webkit-scrollbar-track { + margin-top: -0.125em; + width: 0.5em; + } + &::-webkit-scrollbar-thumb { + background: #332f35; + border-radius: 0.25em; + border: solid 0.125em #232125; //same color as scrollbar back to fake padding + } + &::-webkit-scrollbar-corner { + background: #232125; + } + } + + #sp-quickstart-title { + font-size:27px; + text-transform: uppercase; + font-weight: bold; + } + + .sp-template { + display: flex; + align-items: center; + text-transform: uppercase; + width:16%; + border-radius:5%; + margin-right:4%; + margin-top:4%; + background-color:$basecolor; + + font-size: 18px; + text-align:center; + font-weight: bold; + + &:hover { + cursor:pointer; + background-color:$baseselected; + } + + p { + span { + display:block; + font-size:14px !important; + margin: 0 0 0 0; + padding: 0 0 0 0; + } + width:100%; + + float:left; + position:relative; + } + } + + .sp-template:before { + content:''; + float:left; + padding-top:100%; + } + + #sp-newpixel { + -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ + -moz-box-sizing: border-box; /* Firefox, other Gecko */ + box-sizing: border-box; /* Opera/IE 8+ */ + display: inline-block; + width: 30% !important; + height:65vh; + padding:20px; + position:relative; + background-color:$basecolor; + + #palette-button-splash { + left:5%; + width:90%; + } + + .sp-np-entry { + width:100%; + text-align:center; + } + + input { + border:none; + background-color: #232125; + color:$basetext; + font-size:14px; + width:40px; + padding:7px; + text-align:center; + } + + #create-button { + font-size:18px; + width:150px; + margin-top:40px; + font-weight: bold; + } + + #sp-mode-palette { + text-align: center; + position: relative; + float:bottom; + font-size:16px; + font-weight: bold; + + div.button-menu { + border:2px solid $basetextweak; + border-radius:5px; + position:relative; + display:inline-block; + padding: 0 0 0 0; + text-align:left; + width:90%; + + div { + border:none; + padding:none; + margin:none; + background-color:transparent; + width:50%; + float:left; + text-align: center; + height:25px; + cursor:pointer; + z-index:1; + + p { + z-index:0; + -ms-transform: translateY(-60%); + transform: translateY(-60%); + } + } + + .sp-interface-selected { + background-color: $basetextweak; + } + } + } + } + + #splash-news { + box-sizing: border-box; + padding-left:20px; + width:26%; + height:100%; + background-color:#151516 !important; + float: right; + } + + #latest-update { + width:100%; + font-size:15px; + height:90%; + line-height: 1.5; + position:relative; + top:20px; + + overflow-y:scroll; + box-sizing: border-box; + + img { + width:100%; + } + + &::-webkit-scrollbar { + background: #232125; + width: 0.5em; + } + &::-webkit-scrollbar-track { + margin-top: -0.125em; + width: 0.5em; + } + &::-webkit-scrollbar-thumb { + background: #332f35; + border-radius: 0.25em; + border: solid 0.125em #232125; //same color as scrollbar back to fake padding + } + &::-webkit-scrollbar-corner { + background: #232125; + } + } } \ No newline at end of file diff --git a/debug.log b/debug.log new file mode 100644 index 0000000..3b8e60d --- /dev/null +++ b/debug.log @@ -0,0 +1 @@ +[0114/110029.536:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) diff --git a/images/Logs/line-tool.gif b/images/Logs/line-tool.gif new file mode 100644 index 0000000..6a793f0 Binary files /dev/null and b/images/Logs/line-tool.gif differ diff --git a/images/Logs/palette-block.gif b/images/Logs/palette-block.gif new file mode 100644 index 0000000..d42d45d Binary files /dev/null and b/images/Logs/palette-block.gif differ diff --git a/images/Logs/resize-canvas.gif b/images/Logs/resize-canvas.gif new file mode 100644 index 0000000..b0ffb4c Binary files /dev/null and b/images/Logs/resize-canvas.gif differ diff --git a/images/Logs/scale-sprite.gif b/images/Logs/scale-sprite.gif new file mode 100644 index 0000000..d3cb602 Binary files /dev/null and b/images/Logs/scale-sprite.gif differ diff --git a/images/Logs/splash.gif b/images/Logs/splash.gif new file mode 100644 index 0000000..946ddc5 Binary files /dev/null and b/images/Logs/splash.gif differ diff --git a/images/Logs/trim-canvas.gif b/images/Logs/trim-canvas.gif new file mode 100644 index 0000000..60bd3a8 Binary files /dev/null and b/images/Logs/trim-canvas.gif differ diff --git a/images/Splash images/Caves.png b/images/Splash images/Caves.png new file mode 100644 index 0000000..d90689c Binary files /dev/null and b/images/Splash images/Caves.png differ diff --git a/images/Splash images/Eye.png b/images/Splash images/Eye.png new file mode 100644 index 0000000..3a938f2 Binary files /dev/null and b/images/Splash images/Eye.png differ diff --git a/images/Splash images/Fusionnist.png b/images/Splash images/Fusionnist.png new file mode 100644 index 0000000..1a97b49 Binary files /dev/null and b/images/Splash images/Fusionnist.png differ diff --git a/images/Splash images/Glacier.png b/images/Splash images/Glacier.png new file mode 100644 index 0000000..da4ddb4 Binary files /dev/null and b/images/Splash images/Glacier.png differ diff --git a/images/Splash images/Mountains.png b/images/Splash images/Mountains.png new file mode 100644 index 0000000..f59132d Binary files /dev/null and b/images/Splash images/Mountains.png differ diff --git a/images/Splash images/Polyphorge1.png b/images/Splash images/Polyphorge1.png new file mode 100644 index 0000000..88c859f Binary files /dev/null and b/images/Splash images/Polyphorge1.png differ diff --git a/images/Splash images/Polyphorge2.png b/images/Splash images/Polyphorge2.png new file mode 100644 index 0000000..2643f81 Binary files /dev/null and b/images/Splash images/Polyphorge2.png differ diff --git a/images/Splash images/Rayquaza.png b/images/Splash images/Rayquaza.png new file mode 100644 index 0000000..5b93c46 Binary files /dev/null and b/images/Splash images/Rayquaza.png differ diff --git a/images/Splash images/Sweetie.png b/images/Splash images/Sweetie.png new file mode 100644 index 0000000..1681973 Binary files /dev/null and b/images/Splash images/Sweetie.png differ diff --git a/images/sked.png b/images/sked.png new file mode 100644 index 0000000..2e9cb88 Binary files /dev/null and b/images/sked.png differ diff --git a/js/Util.js b/js/Util.js new file mode 100644 index 0000000..760ca03 --- /dev/null +++ b/js/Util.js @@ -0,0 +1,35 @@ +// Acts as a public static class +class Util { + static getElement(elementOrElementId) { + return typeof elementOrElementId + ? document.getElementById(elementOrElementId) + : elementOrElementId; + } + static getText(elementId) { + return this.getElement(elementId).textContent; + } + + static setText(elementId, text) { + this.getElement(elementId).textContent = text; + } + static getValue(elementId) { + return this.getElement(elementId).value; + } + + static setValue(elementId, value) { + this.getElement(elementId).value = value; + } + //add class .selected to specified element + static select(elementId) { + this.getElement(elementId).classList.add('selected'); + } + + //remove .selected class from specified element + static deselect(elementId) { + this.getElement(elementId).classList.remove('selected'); + } + //toggle the status of the .selected class on the specified element + static toggle(elementId) { + this.getElement(elementId).classList.toggle('selected'); + } +} \ No newline at end of file diff --git a/js/_addColor.js b/js/_addColor.js index ac95af0..943bccd 100644 --- a/js/_addColor.js +++ b/js/_addColor.js @@ -1,8 +1,10 @@ let currentPalette = []; -//adds the given color to the palette -//input hex color string -//returns list item element +/** Adds the given color to the palette + * + * @param {*} newColor the colour to add + * @return the list item containing the added colour + */ function addColor (newColor) { //add # at beginning if not present if (newColor.charAt(0) != '#') @@ -17,6 +19,7 @@ function addColor (newColor) { button.style.backgroundColor = newColor; button.addEventListener('mouseup', clickedColor); listItem.appendChild(button); + listItem.classList.add("draggable-colour") //insert new listItem element at the end of the colors menu (right before add button) colorsMenu.insertBefore(listItem, colorsMenu.children[colorsMenu.children.length-1]); @@ -35,11 +38,19 @@ function addColor (newColor) { //hide edit button button.parentElement.lastChild.classList.add('hidden'); - //show jscolor picker - button.parentElement.firstChild.jscolor.show(); + //show jscolor picker, if basic mode is enabled + if (pixelEditorMode == 'Basic') + button.parentElement.firstChild.jscolor.show(); + else + showDialogue("palette-block", false); }); - console.log(currentPalette); - return listItem; } + +new Sortable(document.getElementById("colors-menu"), { + animation:100, + filter: ".noshrink", + draggable: ".draggable-colour", + onEnd: makeIsDraggingFalse +}); \ No newline at end of file diff --git a/js/_addColorButton.js b/js/_addColorButton.js index 9da3169..7cb5f3f 100644 --- a/js/_addColorButton.js +++ b/js/_addColorButton.js @@ -1,4 +1,4 @@ -//add color button +// add-color-button management on('click', 'add-color-button', function(){ if (!documentCreated) return; diff --git a/js/_algorithms.js b/js/_algorithms.js new file mode 100644 index 0000000..7e94fc8 --- /dev/null +++ b/js/_algorithms.js @@ -0,0 +1,185 @@ +// CONSTS + +// Degrees to radiants +let degreesToRad = Math.PI / 180; +// I'm pretty sure that precision is necessary +let referenceWhite = {x: 95.05, y: 100, z: 108.89999999999999}; + +/**********************SECTION: COLOUR CONVERSIONS****************************** */ + +/** + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param {number} h The hue + * @param {number} s The saturation + * @param {number} l The lightness + * @return {Array} The RGB representation + */ +function cpHslToRgb(h, s, l){ + var r, g, b; + + h /= 360; + s /= 100; + l /= 100; + + if(s == 0){ + r = g = b = l; // achromatic + }else{ + var hue2rgb = function hue2rgb(p, q, t){ + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; +} + +function hsvToRgb(h, s, v) { + var r, g, b; + + h /= 360; + s /= 100; + v /= 100; + var i = Math.floor(h * 6); + var f = h * 6 - i; + var p = v * (1 - s); + var q = v * (1 - f * s); + var t = v * (1 - (1 - f) * s); + + switch (i % 6) { + case 0: r = v, g = t, b = p; break; + case 1: r = q, g = v, b = p; break; + case 2: r = p, g = v, b = t; break; + case 3: r = p, g = q, b = v; break; + case 4: r = t, g = p, b = v; break; + case 5: r = v, g = p, b = q; break; + } + + return [ r * 255, g * 255, b * 255 ]; +} + +function hslToHex(h, s, l) { + h /= 360; + s /= 100; + l /= 100; + let r, g, b; + if (s === 0) { + r = g = b = l; // achromatic + } else { + const hue2rgb = (p, q, t) => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + const toHex = x => { + const hex = Math.round(x * 255).toString(16); + return hex.length === 1 ? '0' + hex : hex; + }; + + return `${toHex(r)}${toHex(g)}${toHex(b)}`; +} + +function rgbToHsl(col) { + let r = col.r; + let g = col.g; + let b = col.b; + + r /= 255, g /= 255, b /= 255; + + let max = Math.max(r, g, b), min = Math.min(r, g, b); + let myH, myS, myL = (max + min) / 2; + + if (max == min) { + myH = myS = 0; // achromatic + } + else { + let d = max - min; + myS = myL > 0.5 ? d / (2 - max - min) : d / (max + min); + + switch (max) { + case r: myH = (g - b) / d + (g < b ? 6 : 0); break; + case g: myH = (b - r) / d + 2; break; + case b: myH = (r - g) / d + 4; break; + } + + myH /= 6; + } + + return {h: myH, s: myS, l: myL }; +} + + function rgbToHsv(col) { + let r = col.r; + let g = col.g; + let b = col.b; + + r /= 255, g /= 255, b /= 255; + + let max = Math.max(r, g, b), min = Math.min(r, g, b); + let myH, myS, myV = max; + + let d = max - min; + myS = max == 0 ? 0 : d / max; + + if (max == min) { + myH = 0; // achromatic + } + else { + switch (max) { + case r: myH = (g - b) / d + (g < b ? 6 : 0); break; + case g: myH = (b - r) / d + 2; break; + case b: myH = (r - g) / d + 4; break; + } + + myH /= 6; + } + + return {h: myH, s: myS, v: myV}; + } + + function RGBtoCIELAB(rgbColour) { + // Convert to XYZ first via matrix transformation + let x = 0.412453 * rgbColour.r + 0.357580 * rgbColour.g + 0.180423 * rgbColour.b; + let y = 0.212671 * rgbColour.r + 0.715160 * rgbColour.g + 0.072169 * rgbColour.b; + let z = 0.019334 * rgbColour.r + 0.119193 * rgbColour.g + 0.950227 * rgbColour.b; + + let xFunc = CIELABconvF(x / referenceWhite.x); + let yFunc = CIELABconvF(y / referenceWhite.y); + let zFunc = CIELABconvF(z / referenceWhite.z); + + let myL = 116 * yFunc - 16; + let myA = 500 * (xFunc - yFunc); + let myB = 200 * (yFunc - zFunc); + + return {l: myL, a: myA, b: myB}; + +} +function CIELABconvF(value) { + if (value > Math.pow(6/29, 3)) { + return Math.cbrt(value); + } + + return 1/3 * Math.pow(6/29, 2) * value + 4/29; +} \ No newline at end of file diff --git a/js/_changeZoom.js b/js/_changeZoom.js index 4dc9eff..8ff9759 100644 --- a/js/_changeZoom.js +++ b/js/_changeZoom.js @@ -1,4 +1,9 @@ -function changeZoom (layer, direction, cursorLocation) { +/** Changes the zoom level of the canvas + * @param {*} direction 'in' or 'out' + * @param {*} cursorLocation The position of the cursor when the user zoomed + */ +function changeZoom (direction, cursorLocation) { + // Computing current width and height var oldWidth = canvasSize[0] * zoom; var oldHeight = canvasSize[1] * zoom; var newWidth, newHeight; @@ -11,7 +16,9 @@ function changeZoom (layer, direction, cursorLocation) { newHeight = canvasSize[1] * zoom; //adjust canvas position - layer.setCanvasOffset(layer.canvas.offsetLeft + (oldWidth - newWidth) *cursorLocation[0]/oldWidth, layer.canvas.offsetTop + (oldHeight - newHeight) *cursorLocation[1]/oldWidth); + layers[0].setCanvasOffset( + layers[0].canvas.offsetLeft + (oldWidth - newWidth) * cursorLocation[0]/oldWidth, + layers[0].canvas.offsetTop + (oldHeight - newHeight) * cursorLocation[1]/oldWidth); } //if you want to zoom in else if (direction == 'in' && zoom + Math.ceil(zoom/10) < window.innerHeight/4){ @@ -20,11 +27,13 @@ function changeZoom (layer, direction, cursorLocation) { newHeight = canvasSize[1] * zoom; //adjust canvas position - layer.setCanvasOffset(layer.canvas.offsetLeft - Math.round((newWidth - oldWidth)*cursorLocation[0]/oldWidth), layer.canvas.offsetTop - Math.round((newHeight - oldHeight)*cursorLocation[1]/oldHeight)); + layers[0].setCanvasOffset( + layers[0].canvas.offsetLeft - Math.round((newWidth - oldWidth)*cursorLocation[0]/oldWidth), + layers[0].canvas.offsetTop - Math.round((newHeight - oldHeight)*cursorLocation[1]/oldHeight)); } //resize canvas - layer.resize(); + layers[0].resize(); // adjust brush size currentTool.updateCursor(); diff --git a/js/_checkerboard.js b/js/_checkerboard.js index 28c28b6..28b15c8 100644 --- a/js/_checkerboard.js +++ b/js/_checkerboard.js @@ -13,7 +13,9 @@ var currentColor = firstCheckerBoardColor; // Saving number of squares filled until now var nSquaresFilled = 0; - +/** Fills the checkerboard canvas with squares with alternating colours + * + */ function fillCheckerboard() { // Getting checkerboard context var context = checkerBoard.context; diff --git a/js/_clickedColor.js b/js/_clickedColor.js index d326818..1f852c7 100644 --- a/js/_clickedColor.js +++ b/js/_clickedColor.js @@ -17,7 +17,7 @@ function clickedColor (e){ e.target.parentElement.classList.add('selected'); } else if (e.which == 3) { //right clicked color - console.log('right clicked color button'); + //console.log('right clicked color button'); //hide edit color button (to prevent it from showing) e.target.parentElement.lastChild.classList.add('hidden'); diff --git a/js/_colorChanged.js b/js/_colorChanged.js index c042dc0..4e31957 100644 --- a/js/_colorChanged.js +++ b/js/_colorChanged.js @@ -23,7 +23,7 @@ on('input', 'jscolor-hex-input', function (e) { //changes all of one color to another after being changed from color picker function colorChanged(e) { - console.log('colorChanged() to ' + e.target.value); + //console.log('colorChanged() to ' + e.target.value); //get colors var newColor = hexToRgb(e.target.value); var oldColor = e.target.oldColor; @@ -41,7 +41,7 @@ function colorChanged(e) { //check if selected color already matches another color colors = document.getElementsByClassName('color-button'); - console.log(colors); + //console.log(colors); var colorCheckingStyle = 'background: #bc60c4; color: white'; var newColorHex = e.target.value.toLowerCase(); @@ -54,11 +54,11 @@ function colorChanged(e) { //if generated color matches this color if (newColorHex == colors[i].jscolor.toString()) { - console.log('%ccolor already exists'+(colors[i].parentElement.classList.contains('jscolor-active')?' (but is the current color)':''), colorCheckingStyle); + //console.log('%ccolor already exists'+(colors[i].parentElement.classList.contains('jscolor-active')?' (but is the current color)':''), colorCheckingStyle); //if the color isnt the one that has the picker currently open if (!colors[i].parentElement.classList.contains('jscolor-active')) { - console.log('%cColor is duplicate', colorCheckingStyle); + //console.log('%cColor is duplicate', colorCheckingStyle); //show the duplicate color warning duplicateColorWarning.style.visibility = 'visible'; diff --git a/js/_colorPicker.js b/js/_colorPicker.js new file mode 100644 index 0000000..007f204 --- /dev/null +++ b/js/_colorPicker.js @@ -0,0 +1,806 @@ +let sliders = document.getElementsByClassName("cp-slider-entry"); +let colourPreview = document.getElementById("cp-colour-preview"); +let colourValue = document.getElementById("cp-hex"); +let currentPickerMode = "rgb"; +let currentPickingMode = "mono"; +let styleElement = document.createElement("style"); +let miniPickerCanvas = document.getElementById("cp-spectrum"); +let miniPickerSlider = document.getElementById("cp-minipicker-slider"); +let activePickerIcon = document.getElementById("cp-active-icon"); +let pickerIcons = [activePickerIcon]; +let hexContainers = [document.getElementById("cp-colours-previews").children[0],null,null,null]; +let startPickerIconPos = [[0,0],[0,0],[0,0],[0,0]]; +let currPickerIconPos = [[0,0], [0,0],[0,0],[0,0]]; +let styles = ["",""]; +let draggingCursor = false; + +cpInit(); + +function cpInit() { + // Appending the palette styles + document.getElementsByTagName("head")[0].appendChild(styleElement); + + // Saving first icon position + startPickerIconPos[0] = [miniPickerCanvas.getBoundingClientRect().left, miniPickerCanvas.getBoundingClientRect().top]; + // Set the correct size of the canvas + miniPickerCanvas.height = miniPickerCanvas.getBoundingClientRect().height; + miniPickerCanvas.width = miniPickerCanvas.getBoundingClientRect().width; + + // Update picker position + updatePickerByHex(colourValue.value); + // Startup updating + updateAllSliders(); + // Fill minislider + updateMiniSlider(colourValue.value); + // Fill minipicker + updatePickerByHex(colourValue.value); + + updateMiniPickerSpectrum(); +} + +function hexUpdated() { + updatePickerByHex(colourValue.value); + updateSlidersByHex(colourValue.value); +} + +// Applies the styles saved in the style array to the style element in the head of the document +function updateStyles() { + styleElement.innerHTML = styles[0] + styles[1]; +} + +/** Updates the background gradients of the sliders given their value + * Updates the hex colour and its preview + * Updates the minipicker according to the computed hex colour + * + */ +function updateSliderValue (sliderIndex, updateMini = true) { + let toUpdate; + let slider; + let input; + let hexColour; + let sliderValues; + + toUpdate = sliders[sliderIndex - 1]; + + slider = toUpdate.getElementsByTagName("input")[0]; + input = toUpdate.getElementsByTagName("input")[1]; + + // Update label value + input.value = slider.value; + + // Update preview colour + // get slider values + sliderValues = getSlidersValues(); + + // Generate preview colour + switch (currentPickerMode) { + case 'rgb': + hexColour = rgbToHex(sliderValues[0], sliderValues[1], sliderValues[2]); + break; + case 'hsv': + let tmpRgb = hsvToRgb(sliderValues[0], sliderValues[1], sliderValues[2]); + hexColour = rgbToHex(parseInt(tmpRgb[0]), parseInt(tmpRgb[1]), parseInt(tmpRgb[2])); + break; + case 'hsl': + hexColour = hslToHex(sliderValues[0], sliderValues[1], sliderValues[2]); + break; + default: + console.log("wtf select a decent picker mode"); + return; + } + // Update preview colour div + colourPreview.style.backgroundColor = '#' + hexColour; + colourValue.value = '#' + hexColour; + + // Update sliders background + // there's no other way than creating a custom css file, appending it to the head and + // specify the sliders' backgrounds here + + styles[0] = ''; + for (let i=0; i -8 && top > -8 && left < canvasRect.width-8 && top < canvasRect.height-8){ + activePickerIcon.style["left"] = "" + left + "px"; + activePickerIcon.style["top"]= "" + top + "px"; + + currPickerIconPos[0] = [left, top]; + } + + updateMiniPickerColour(); + updateOtherIcons(); + } +} + +// Updates the main sliders given a hex value computed with the minipicker +function updateSlidersByHex(hex, updateMini = true) { + let colour; + let mySliders = [sliders[0].getElementsByTagName("input")[0], + sliders[1].getElementsByTagName("input")[0], + sliders[2].getElementsByTagName("input")[0]]; + + switch (currentPickerMode) { + case 'rgb': + colour = hexToRgb(hex); + + mySliders[0].value = colour.r; + mySliders[1].value = colour.g; + mySliders[2].value = colour.b; + + break; + case 'hsv': + colour = rgbToHsv(hexToRgb(hex)); + + mySliders[0].value = colour.h * 360; + mySliders[1].value = colour.s * 100; + mySliders[2].value = colour.v * 100; + + break; + case 'hsl': + colour = rgbToHsl(hexToRgb(hex)); + + mySliders[0].value = colour.h * 360; + mySliders[1].value = colour.s * 100; + mySliders[2].value = colour.l * 100; + + break; + default: + break; + } + + updateAllSliders(false); +} + +// Gets the position of the picker cursor relative to the canvas +function getCursorPosMinipicker(e) { + var x; + var y; + + if (e.pageX != undefined && e.pageY != undefined) { + x = e.pageX; + y = e.pageY; + } + else { + x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + + x -= miniPickerCanvas.offsetLeft; + y -= miniPickerCanvas.offsetTop; + + return [Math.round(x), Math.round(y)]; +} + +// Updates the minipicker given a hex computed by the main sliders +// Moves the cursor +function updatePickerByHex(hex) { + let hsv = rgbToHsv(hexToRgb(hex)); + let xPos = miniPickerCanvas.width * hsv.h - 8; + let yPos = miniPickerCanvas.height * hsv.s + 8; + + miniPickerSlider.value = hsv.v * 100; + + currPickerIconPos[0][0] = xPos; + currPickerIconPos[0][1] = miniPickerCanvas.height - yPos; + + if (currPickerIconPos[0][1] >= 92) + { + currPickerIconPos[0][1] = 91.999; + } + + activePickerIcon.style.left = '' + xPos + 'px'; + activePickerIcon.style.top = '' + (miniPickerCanvas.height - yPos) + 'px'; + activePickerIcon.style.backgroundColor = '#' + getMiniPickerColour(); + + colourPreview.style.backgroundColor = hex; + + updateOtherIcons(); + updateMiniSlider(hex); +} + +// Fired when the value of the minislider changes: updates the spectrum gradient and the hex colour +function miniSliderInput(event) { + let newHex; + let newHsv = rgbToHsv(hexToRgb(getMiniPickerColour())); + let rgb; + + // Adding slider value to value + newHsv.v = parseInt(event.target.value); + // Updating hex + rgb = hsvToRgb(newHsv.h * 360, newHsv.s * 100, newHsv.v); + newHex = rgbToHex(Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2])); + + colourValue.value = newHex; + + updateMiniPickerSpectrum(); + updateMiniPickerColour(); +} + +// Updates the hex colour after having changed the minislider (MERGE) +function updateMiniPickerColour() { + let hex = getMiniPickerColour(); + + activePickerIcon.style.backgroundColor = '#' + hex; + + // Update hex and sliders based on hex + colourValue.value = '#' + hex; + colourPreview.style.backgroundColor = '#' + hex; + + updateSlidersByHex(hex); + updateMiniSlider(hex); + updateOtherIcons(); +} + +// Returns the current colour of the minipicker +function getMiniPickerColour() { + let hex; + let pickedColour; + + pickedColour = miniPickerCanvas.getContext('2d').getImageData(currPickerIconPos[0][0] + 8, + currPickerIconPos[0][1] + 8, 1, 1).data; + + hex = rgbToHex(pickedColour[0], pickedColour[1], pickedColour[2]); + + return hex; +} + +// Update the background gradient of the slider in the minipicker +function updateMiniSlider(hex) { + let rgb = hexToRgb(hex); + + styles[1] = "input[type=range]#cp-minipicker-slider::-webkit-slider-runnable-track { background: rgb(2,0,36);"; + styles[1] += "background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(0,0,0,1) 0%, " + + "rgba(" + rgb.r + "," + rgb.g + "," + rgb.b + ",1) 100%);}"; + + updateMiniPickerSpectrum(); + updateStyles(); +} + +// Updates the gradient of the spectrum canvas in the minipicker +function updateMiniPickerSpectrum() { + let ctx = miniPickerCanvas.getContext('2d'); + let hsv = rgbToHsv(hexToRgb(colourValue.value)); + let tmp; + let white = {h:hsv.h * 360, s:0, v: parseInt(miniPickerSlider.value)}; + + white = hsvToRgb(white.h, white.s, white.v); + + ctx.clearRect(0, 0, miniPickerCanvas.width, miniPickerCanvas.height); + + // Drawing hues + var hGrad = ctx.createLinearGradient(0, 0, miniPickerCanvas.width, 0); + + for (let i=0; i<7; i++) { + tmp = hsvToRgb(60 * i, 100, hsv.v * 100); + hGrad.addColorStop(i / 6, '#' + rgbToHex(Math.round(tmp[0]), Math.round(tmp[1]), Math.round(tmp[2]))); + } + ctx.fillStyle = hGrad; + ctx.fillRect(0, 0, miniPickerCanvas.width, miniPickerCanvas.height); + + // Drawing sat / lum + var vGrad = ctx.createLinearGradient(0, 0, 0, miniPickerCanvas.height); + vGrad.addColorStop(0, 'rgba(' + white[0] +',' + white[1] + ',' + white[2] + ',0)'); + /* + vGrad.addColorStop(0.1, 'rgba(255,255,255,0)'); + vGrad.addColorStop(0.9, 'rgba(255,255,255,1)'); + */ + vGrad.addColorStop(1, 'rgba(' + white[0] +',' + white[1] + ',' + white[2] + ',1)'); + + ctx.fillStyle = vGrad; + ctx.fillRect(0, 0, miniPickerCanvas.width, miniPickerCanvas.height); +} + +function toggleDraggingCursor() { + draggingCursor = !draggingCursor; +} + +function changePickingMode(event, newMode) { + let nIcons = pickerIcons.length; + let canvasContainer = document.getElementById("cp-canvas-container"); + // Number of hex containers to add + let nHexContainers; + + // Remove selected class from previous mode + document.getElementById("cp-colour-picking-modes").getElementsByClassName("cp-selected-mode")[0].classList.remove("cp-selected-mode"); + // Updating mode + currentPickingMode = newMode; + // Adding selected class to new mode + event.target.classList.add("cp-selected-mode"); + + for (let i=1; i 110) { + return '#332f35' + } + else { + return '#c2bbc7'; + } + + //take in a color and return its brightness + function colorBrightness (color) { + var r = parseInt(color.slice(1, 3), 16); + var g = parseInt(color.slice(3, 5), 16); + var b = parseInt(color.slice(5, 7), 16); + return Math.round(((parseInt(r) * 299) + (parseInt(g) * 587) + (parseInt(b) * 114)) / 1000); + } +} \ No newline at end of file diff --git a/js/_copyPaste.js b/js/_copyPaste.js index 90e2a70..e97215f 100644 --- a/js/_copyPaste.js +++ b/js/_copyPaste.js @@ -1,11 +1,17 @@ +// Data saved when copying or cutting let clipboardData; +// Tells if the user is pasting something or not let isPasting = false; +// Coordinates of the copied (or cut) selection let copiedStartX; let copiedStartY; let copiedEndX; let copiedEndY; +/** Copies the current selection to the clipboard + * + */ function copySelection() { copiedEndX = endX; copiedEndY = endY; @@ -16,13 +22,19 @@ function copySelection() { clipboardData = currentLayer.context.getImageData(startX, startY, endX - startX + 1, endY - startY + 1); } +/** Pastes the clipboard data onto the current layer + * + */ function pasteSelection() { // Can't paste if the layer is locked if (currentLayer.isLocked) { return; } + + // Cancel the current selection endSelection(); + // I'm pasting isPasting = true; // Putting the image data on the tmp layer TMPLayer.context.putImageData(clipboardData, copiedStartX, copiedStartY); @@ -43,9 +55,11 @@ function pasteSelection() { //drawRect(copiedStartX, copiedEndX, copiedStartY, copiedEndY); } +/** Cuts the current selection and copies it to the clipboard + * + */ function cutSelectionTool() { - console.log("Taglio"); - + // Saving the coordinates copiedEndX = endX; copiedEndY = endY; @@ -53,13 +67,19 @@ function cutSelectionTool() { copiedStartY = startY; // Getting the selected pixels + // If I'm already moving a selection if (imageDataToMove !== undefined) { + // I just save that selection in the clipboard clipboardData = imageDataToMove; + // And clear the underlying space TMPLayer.context.clearRect(0, 0, TMPLayer.canvas.width, TMPLayer.canvas.height); + // The image has been cleared, so I don't have anything to move anymore imageDataToMove = undefined; } else { - clipboardData = currentLayer.context.getImageData(startX, startY, endX - startX + 1, endY - startY + 1); + // Otherwise, I copy the current selection into the clipboard + copySelection(); + // And clear the selection currentLayer.context.clearRect(startX - 0.5, startY - 0.5, endX - startX + 1, endY - startY + 1); } } \ No newline at end of file diff --git a/js/_createButton.js b/js/_createButton.js index 866edc4..23b807f 100644 --- a/js/_createButton.js +++ b/js/_createButton.js @@ -1,7 +1,51 @@ +function create(isSplash) { + var splashPostfix = ''; + // If I'm creating from the splash menu, I append '-splash' so I get the corresponding values + if (isSplash) { + splashPostfix = '-splash'; + } + + var width = getValue('size-width' + splashPostfix); + var height = getValue('size-height' + splashPostfix); + + // If I'm creating from the splash screen, I use the splashMode variable + var mode = isSplash ? splashMode : getValue('editor-mode'); + + newPixel(width, height, mode); + + // If I'm not creating from the splash page, then this is not the first project I've created + if (!isSplash) + document.getElementById('new-pixel-warning').style.display = 'block'; + + //get selected palette name + var selectedPalette = getText('palette-button' + splashPostfix); + if (selectedPalette == 'Choose a palette...') + selectedPalette = 'none'; + + //track google event + ga('send', 'event', 'Pixel Editor New', selectedPalette, width+'/'+height); /*global ga*/ + + + //reset new form + setValue('size-width', 64); + setValue('size-height', 64); + setValue("editor-mode", 'Advanced') + + setText('editor-mode-button', 'Choose a mode...'); + setText('palette-button', 'Choose a palette...'); + setText('preset-button', 'Choose a preset...'); +} + +/** Triggered when the "Create" button in the new pixel dialogue is pressed + * + */ on('click', 'create-button', function (){ + // Getting the values of the form var width = getValue('size-width'); var height = getValue('size-height'); var mode = getValue("editor-mode"); + + // Creating a new pixel with those properties newPixel(width, height, mode); document.getElementById('new-pixel-warning').style.display = 'block'; @@ -23,3 +67,37 @@ on('click', 'create-button', function (){ setText('palette-button', 'Choose a palette...'); setText('preset-button', 'Choose a preset...'); }); + +/** Triggered when the "Create" button in the new pixel dialogue is pressed + * + */ +on('click', 'create-button-splash', function (){ + // Getting the values of the form + var width = getValue('size-width-splash'); + var height = getValue('size-height-splash'); + var mode = pixelEditorMode; + + if (mode == 'Advanced') + mode = "Basic"; + else + mode = "Advanced"; + + // Creating a new pixel with those properties + newPixel(width, height, mode); + + //track google event + ga('send', 'event', 'Pixel Editor New', selectedPalette, width+'/'+height); /*global ga*/ + document.getElementById('new-pixel-warning').style.display = 'block'; + + // Resetting the new pixel values + selectedPalette = 'none'; + + //reset new pixel form + setValue('size-width-splash', 64); + setValue('size-height-splash', 64); + setValue("editor-mode", 'Advanced') + + setText('editor-mode-button', 'Choose a mode...'); + setText('palette-button', 'Choose a palette...'); + setText('preset-button', 'Choose a preset...'); +}); diff --git a/js/_createColorPalette.js b/js/_createColorPalette.js index 8f7352b..a8cf769 100644 --- a/js/_createColorPalette.js +++ b/js/_createColorPalette.js @@ -1,5 +1,11 @@ -function createColorPalette(paletteColors, fillBackground, deletePreviousPalette = true) { +/** Creates the colour palette + * + * @param {*} paletteColors The colours of the palette + * @param {*} deletePreviousPalette Tells if the app should delete the previous palette or not + * (used when opening a file, for example) + */ +function createColorPalette(paletteColors, deletePreviousPalette = true) { //remove current palette if (deletePreviousPalette) { colors = document.getElementsByClassName('color-button'); @@ -11,6 +17,7 @@ function createColorPalette(paletteColors, fillBackground, deletePreviousPalette var lightestColor = '#000000'; var darkestColor = '#ffffff'; + // Adding all the colours in the array for (var i = 0; i < paletteColors.length; i++) { var newColor = paletteColors[i]; var newColorElement = addColor(newColor); @@ -42,6 +49,9 @@ function createColorPalette(paletteColors, fillBackground, deletePreviousPalette currentLayer.context.fillStyle = darkestColor; } +/** Creates the palette with the colours used in all the layers + * + */ function createPaletteFromLayers() { let colors = {}; @@ -68,8 +78,6 @@ function createPaletteFromLayers() { } } - console.log(colors); - //create array out of colors object let colorPaletteArray = []; for (let color in colors) { @@ -77,8 +85,7 @@ function createPaletteFromLayers() { colorPaletteArray.push('#'+rgbToHex(colors[color])); } } - console.log('COLOR PALETTE ARRAY', colorPaletteArray); - //create palette form colors array - createColorPalette(colorPaletteArray, false); + //create palette from colors array + createColorPalette(colorPaletteArray, true); } \ No newline at end of file diff --git a/js/_deleteColor.js b/js/_deleteColor.js index 1abb9dd..7656d30 100644 --- a/js/_deleteColor.js +++ b/js/_deleteColor.js @@ -9,19 +9,19 @@ function deleteColor (color) { //if color is a string, then find the corresponding button if (typeof color === 'string') { - console.log('trying to find ',color); + //console.log('trying to find ',color); //get all colors in palette colors = document.getElementsByClassName('color-button'); //loop through colors for (var i = 0; i < colors.length; i++) { - console.log(color,'=',colors[i].jscolor.toString()); + //console.log(color,'=',colors[i].jscolor.toString()); if (color == colors[i].jscolor.toString()) { - console.log('match'); + //console.log('match'); //set color to the color button color = colors[i]; - console.log('found color', color); + //console.log('found color', color); //exit loop break; @@ -30,7 +30,7 @@ function deleteColor (color) { //if the color wasn't found if (typeof color === 'string') { - console.log('color not found'); + //console.log('color not found'); //exit function return; } diff --git a/js/_dialogue.js b/js/_dialogue.js index ba3a741..8157040 100644 --- a/js/_dialogue.js +++ b/js/_dialogue.js @@ -1,27 +1,55 @@ +let currentOpenDialogue = ""; + +/** Shows the dialogue window called dialogueName, which is a child of pop-up-container in pixel-editor.hbs + * + * @param {*} dialogueName The name of the window to show + * @param {*} trackEvent Should I track the GA event? + */ function showDialogue (dialogueName, trackEvent) { if (typeof trackEvent === 'undefined') trackEvent = true; + // Updating currently open dialogue + currentOpenDialogue = dialogueName; + // The pop up window is open dialogueOpen = true; + // Showing the pop up container popUpContainer.style.display = 'block'; + // Showing the window document.getElementById(dialogueName).style.display = 'block'; + // If I'm opening the palette window, I initialize the colour picker + if (dialogueName == 'palette-block' && documentCreated) { + cpInit(); + pbInit(); + } + //track google event if (trackEvent) ga('send', 'event', 'Palette Editor Dialogue', dialogueName); /*global ga*/ } +/** Closes the current dialogue by hiding the window and the pop-up-container + * + */ function closeDialogue () { popUpContainer.style.display = 'none'; - var popups = popUpContainer.children; + for (var i = 0; i < popups.length; i++) { popups[i].style.display = 'none'; } dialogueOpen = false; + + if (currentOpenDialogue == "palette-block") { + pbAddToSimplePalette(); + } } +/** Closes a dialogue window if the user clicks everywhere but in the current window + * + */ popUpContainer.addEventListener('click', function (e) { if (e.target == popUpContainer) closeDialogue(); @@ -31,6 +59,6 @@ popUpContainer.addEventListener('click', function (e) { var cancelButtons = popUpContainer.getElementsByClassName('close-button'); for (var i = 0; i < cancelButtons.length; i++) { cancelButtons[i].addEventListener('click', function () { - closeDialogue(); + closeDialogue(); }); } diff --git a/js/_drawLine.js b/js/_drawLine.js index e7ce712..ab2f392 100644 --- a/js/_drawLine.js +++ b/js/_drawLine.js @@ -1,4 +1,4 @@ -//draw a line between two points on canvas +//draws a line between two points on canvas function line(x0,y0,x1,y1, brushSize) { var dx = Math.abs(x1-x0); var dy = Math.abs(y1-y0); @@ -9,7 +9,7 @@ function line(x0,y0,x1,y1, brushSize) { while (true) { //set pixel // If the current tool is the brush - if (currentTool.name == 'pencil' || currentTool.name == 'rectangle') { + if (currentTool.name == 'pencil' || currentTool.name == 'rectangle' || currentTool.name == 'ellipse') { // I fill the rect currentLayer.context.fillRect(x0-Math.floor(brushSize/2), y0-Math.floor(brushSize/2), brushSize, brushSize); } else if (currentTool.name == 'eraser') { diff --git a/js/_editorMode.js b/js/_editorMode.js index e89626c..ccb8dcf 100644 --- a/js/_editorMode.js +++ b/js/_editorMode.js @@ -3,27 +3,46 @@ let modes = { description: 'Basic mode is perfect if you want to create simple sprites or try out palettes.' }, 'Advanced' : { - description: 'Choose advanced mode to gain access to features such as layers.' + description: 'Choose advanced mode to gain access to more advanced features such as layers.' } } let infoBox = document.getElementById('editor-mode-info'); +let currentSplashButton = document.getElementById("sp-mode-palette").children[0].children[1]; -function switchMode(currentMode, mustConfirm = true) { +function splashMode(mouseEvent, mode) { + if (currentSplashButton == undefined) { + currentSplashButton = mouseEvent.target.parentElement; + } + + if (mode !== pixelEditorMode) { + // Remove selected class to old button + currentSplashButton.classList.remove("sp-interface-selected"); + // Add selected class to new button + mouseEvent.target.parentElement.classList.add("sp-interface-selected"); + + // Setting the new mode + pixelEditorMode = mode; + } + + // Setting the new selected button + currentSplashButton = mouseEvent.target.parentElement; +} + +function switchMode(mustConfirm = true) { //switch to advanced mode - if (currentMode == 'Basic') { + if (pixelEditorMode == 'Basic') { // Switch to advanced ez pez lemon squez document.getElementById('switch-mode-button').innerHTML = 'Switch to basic mode'; // Show the layer menus layerList.style.display = "inline-block"; document.getElementById('layer-button').style.display = 'inline-block'; - // Move the palette menu - document.getElementById('colors-menu').style.right = '200px'; + // Hide the palette menu + document.getElementById('colors-menu').style.right = '200px' pixelEditorMode = 'Advanced'; } - //switch to basic mode else { //if there is a current layer (a document is active) @@ -47,15 +66,17 @@ function switchMode(currentMode, mustConfirm = true) { // Hide the layer menus layerList.style.display = 'none'; document.getElementById('layer-button').style.display = 'none'; - // Move the palette menu - document.getElementById('colors-menu').style.right = '0px'; + // Show the palette menu + document.getElementById('colors-menu').style.display = 'flex'; + // Move the palette menu + document.getElementById('colors-menu').style.right = '0px'; pixelEditorMode = 'Basic'; } } on('click', 'switch-mode-button', function (e) { - switchMode(pixelEditorMode); + switchMode(); }); // Makes the menu open diff --git a/js/_ellipse.js b/js/_ellipse.js new file mode 100644 index 0000000..11a6ae9 --- /dev/null +++ b/js/_ellipse.js @@ -0,0 +1,146 @@ +// Saving the empty rect svg +var emptyEllipseSVG = document.getElementById("ellipse-empty-button-svg"); +// and the full rect svg so that I can change them when the user changes rect modes +var fullEllipseSVG = document.getElementById("ellipse-full-button-svg"); + +// The start mode is empty ellipse +var ellipseDrawMode = 'empty'; +// I'm not drawing a ellipse at the beginning +var isDrawingEllipse = false; + +// Ellipse coordinates +let startEllipseX; +let startEllipseY; +let endEllipseX; +let endEllipseY; + +// TODO: [ELLIPSE] Make it draw ellipse instead of copy-pasted rectangle +/** Starts drawing the ellipse, saves the start coordinates + * + * @param {*} mouseEvent + */ +function startEllipseDrawing(mouseEvent) { + // Putting the vfx layer on top of everything + VFXCanvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex, 10) + 1;; + // Updating flag + isDrawingEllipse = true; + + // Saving the start coords of the ellipse + let cursorPos = getCursorPosition(mouseEvent); + startEllipseX = Math.floor(cursorPos[0] / zoom) + 0.5; + startEllipseY = Math.floor(cursorPos[1] / zoom) + 0.5; + + drawEllipse(startEllipseX, startEllipseY); +} + +// TODO: [ELLIPSE] Make it draw ellipse instead of copy-pasted rectangle +/** Updates the ellipse preview depending on the position of the mouse + * + * @param {*} mouseEvent The mouseEvent from which we'll get the mouse position + */ +function updateEllipseDrawing(mouseEvent) { + let pos = getCursorPosition(mouseEvent); + + // Drawing the ellipse at the right position + drawEllipse(Math.round(pos[0] / zoom) + 0.5, Math.round(pos[1] / zoom) + 0.5); +} + +// TODO: [ELLIPSE] Make it draw ellipse instead of copy-pasted rectangle +/** Finishes drawing the ellipse, decides the end coordinates and moves the preview ellipse to the + * current layer + * + * @param {*} mouseEvent event from which we'll get the mouse position + */ +function endEllipseDrawing(mouseEvent) { + // Getting the end position + let currentPos = getCursorPosition(mouseEvent); + let vfxContext = VFXCanvas.getContext("2d"); + + endEllipseX = Math.round(currentPos[0] / zoom) + 0.5; + endEllipseY = Math.round(currentPos[1] / zoom) + 0.5; + + // Inverting end and start (start must always be the top left corner) + if (endEllipseX < startEllipseX) { + let tmp = endEllipseX; + endEllipseX = startEllipseX; + startEllipseX = tmp; + } + // Same for the y + if (endEllipseY < startEllipseY) { + let tmp = endEllipseY; + endEllipseY = startEllipseY; + startEllipseY = tmp; + } + + // Resetting this + isDrawingEllipse = false; + // Drawing the ellipse + startEllipseY -= 0.5; + endEllipseY -= 0.5; + endEllipseX -= 0.5; + startEllipseX -= 0.5; + + // Setting the correct linewidth and colour + currentLayer.context.lineWidth = tool.ellipse.brushSize; + currentLayer.context.fillStyle = currentGlobalColor; + + // Drawing the ellipse using 4 lines + line(startEllipseX, startEllipseY, endEllipseX, startEllipseY, tool.ellipse.brushSize); + line(endEllipseX, startEllipseY, endEllipseX, endEllipseY, tool.ellipse.brushSize); + line(endEllipseX, endEllipseY, startEllipseX, endEllipseY, tool.ellipse.brushSize); + line(startEllipseX, endEllipseY, startEllipseX, startEllipseY, tool.ellipse.brushSize); + + // If I have to fill it, I do so + if (ellipseDrawMode == 'fill') { + currentLayer.context.fillRect(startEllipseX, startEllipseY, endEllipseX - startEllipseX, endEllipseY - startEllipseY); + } + + // Clearing the vfx canvas + vfxContext.clearRect(0, 0, VFXCanvas.width, VFXCanvas.height); +} + +// TODO: [ELLIPSE] Make it draw ellipse instead of copy-pasted rectangle +/** Draws a ellipse with end coordinates given by x and y on the VFX layer (draws + * the preview for the ellipse tool) + * + * @param {*} x The current end x of the ellipse + * @param {*} y The current end y of the ellipse + */ +function drawEllipse(x, y) { + // Getting the vfx context + let vfxContext = VFXCanvas.getContext("2d"); + + // Clearing the vfx canvas + vfxContext.clearRect(0, 0, VFXCanvas.width, VFXCanvas.height); + + // Drawing the ellipse + vfxContext.lineWidth = tool.ellipse.brushSize; + vfxContext.strokeStyle = currentGlobalColor; + + // Drawing the ellipse + vfxContext.beginPath(); + if ((tool.ellipse.brushSize % 2 ) == 0) { + vfxContext.rect(startEllipseX - 0.5, startEllipseY - 0.5, x - startEllipseX, y - startEllipseY); + } + else { + vfxContext.rect(startEllipseX, startEllipseY, x - startEllipseX, y - startEllipseY); + } + + vfxContext.setLineDash([]); + vfxContext.stroke(); +} + +/** Sets the correct tool icon depending on its mode + * + */ +function setEllipseToolSvg() { + console.log("set eilipse svg"); + if (ellipseDrawMode == 'empty') { + emptyEllipseSVG.setAttribute('display', 'visible'); + fullEllipseSVG.setAttribute('display', 'none'); + } + else { + emptyEllipseSVG.setAttribute('display', 'none'); + fullEllipseSVG.setAttribute('display', 'visible'); + } +} diff --git a/js/_featureToggles.js b/js/_featureToggles.js new file mode 100644 index 0000000..5b77f57 --- /dev/null +++ b/js/_featureToggles.js @@ -0,0 +1,33 @@ +const featureToggles = (function featureTogglesModule() { + + const ellipseToolLocalStorageKey = 'feature_ellipseTool'; + + return { + onLoad: () => { + updateEllipseToolVisibility() + }, + enableEllipseTool, + disableEllipseTool + } + + //////// + + function updateEllipseToolVisibility() { + // TODO: [ELLIPSE] Once ellipse is ready for release make it enabled by default + const isEllipseToolEnabled = (window.localStorage.getItem(ellipseToolLocalStorageKey) === "yes") || false; + const ellipseToolElement = document.getElementById("tools-menu--ellipse"); + ellipseToolElement.style.display = isEllipseToolEnabled ? 'block' : 'none'; + } + + function enableEllipseTool() { + window.localStorage.setItem(ellipseToolLocalStorageKey, "yes"); + updateEllipseToolVisibility(); + } + + function disableEllipseTool() { + window.localStorage.setItem(ellipseToolLocalStorageKey, "no"); + updateEllipseToolVisibility(); + } + +})(); + diff --git a/js/_featuresLog.js b/js/_featuresLog.js new file mode 100644 index 0000000..5fd6b89 --- /dev/null +++ b/js/_featuresLog.js @@ -0,0 +1 @@ +showDialogue("splash", false); \ No newline at end of file diff --git a/js/_fileMenu.js b/js/_fileMenu.js index f680dcf..16c6b1c 100644 --- a/js/_fileMenu.js +++ b/js/_fileMenu.js @@ -7,19 +7,14 @@ for (var i = 1; i < mainMenuItems.length; i++) { var menuItem = mainMenuItems[i]; var menuButton = menuItem.children[0]; - console.log(mainMenuItems); - //when you click a main menu items button on('click', menuButton, function (e, button) { - console.log('parent ', button.parentElement); select(button.parentElement); }); var subMenu = menuItem.children[1]; var subMenuItems = subMenu.children; - - //when you click an item within a menu button for (var j = 0; j < subMenuItems.length; j++) { diff --git a/js/_fill.js b/js/_fill.js index aa8a7d9..266c364 100644 --- a/js/_fill.js +++ b/js/_fill.js @@ -7,11 +7,6 @@ function fill(cursorLocation) { tempImage.data[pixelPos + 1] = fillColor.g; tempImage.data[pixelPos + 2] = fillColor.b; tempImage.data[pixelPos + 3] = 255; - /* - tempImage.data[pixelPos] = fillColor.r; - tempImage.data[pixelPos + 1] = fillColor.g; - tempImage.data[pixelPos + 2] = fillColor.b; - */ } //change x y to color value passed from the function and use that as the original color @@ -21,8 +16,9 @@ function fill(cursorLocation) { var r = tempImage.data[pixelPos]; var g = tempImage.data[pixelPos + 1]; var b = tempImage.data[pixelPos + 2]; + var a = tempImage.data[pixelPos + 3]; //console.log(r == color[0] && g == color[1] && b == color[2]); - return (r == color[0] && g == color[1] && b == color[2]); + return (r == color[0] && g == color[1] && b == color[2] && a == color[3]); } //save history state @@ -41,16 +37,21 @@ function fill(cursorLocation) { var startingPosition = (topmostPixelsArray[0][1] * canvasSize[0] + topmostPixelsArray[0][0]) * 4; //the color of the cluster that is being filled - var clusterColor = [tempImage.data[startingPosition],tempImage.data[startingPosition+1],tempImage.data[startingPosition+2]]; + var clusterColor = [tempImage.data[startingPosition],tempImage.data[startingPosition+1],tempImage.data[startingPosition+2], tempImage.data[startingPosition+3]]; //the new color to fill with var fillColor = hexToRgb(currentLayer.context.fillStyle); + console.log("here"); //if you try to fill with the same color that's already there, exit the function if (clusterColor[0] == fillColor.r && clusterColor[1] == fillColor.g && - clusterColor[2] == fillColor.b ) - return; + clusterColor[2] == fillColor.b && + clusterColor[3] != 0) { + console.log("Returned"); + return; + } + //loop until there are no more values left in this array while (topmostPixelsArray.length) { diff --git a/js/_getCursorPosition.js b/js/_getCursorPosition.js index 4c9cdca..6aaed4e 100644 --- a/js/_getCursorPosition.js +++ b/js/_getCursorPosition.js @@ -1,4 +1,4 @@ -//get cursor position relative to canvas +//gets cursor position relative to canvas function getCursorPosition(e) { var x; var y; diff --git a/js/_history.js b/js/_history.js index 656d05d..ffc5fb6 100644 --- a/js/_history.js +++ b/js/_history.js @@ -1,3 +1,17 @@ +/** How the history works + * - undoStates stores the states that can be undone + * - redoStates stores the states that can be redone + * - undo() undoes an action and adds it to the redoStates + * - redo() redoes an action and adds it to the undoStates + * - Each HistoryState must implement an undo() and redo() function + * Those functions actually implement the undo and redo mechanism for that action, + * so you'll need to save the data you need as attributes in the constructor. For example, + * for the HistoryStateAddColour, the added colour is saved so that it can be removed in + * undo() or added back in redo(). + * - Each HistoryState must call saveHistoryState(this) so that it gets added to the stack + * + */ + var undoStates = []; var redoStates = []; @@ -28,6 +42,8 @@ function HistoryStateResizeSprite(xRatio, yRatio, algo, oldData) { }; this.redo = function() { + console.log("REDOOOO"); + console.log("Ratio: " + this.xRatio + "," + this.yRatio); currentAlgo = algo; resizeSprite(null, [this.xRatio, this.yRatio]); undoStates.push(this); @@ -44,7 +60,6 @@ function HistoryStateResizeCanvas(newSize, oldSize, imageDatas, trim) { this.undo = function() { let dataIndex = 0; - console.log("breakpoint"); // Resizing the canvas resizeCanvas(null, oldSize, null, false); // Putting the image datas @@ -59,7 +74,6 @@ function HistoryStateResizeCanvas(newSize, oldSize, imageDatas, trim) { }; this.redo = function() { - console.log("trim: " + this.trim); if (!this.trim) { resizeCanvas(null, newSize, null, false); } @@ -101,7 +115,6 @@ function HistoryStateFlattenTwoVisibles(belowImageData, afterAbove, layerIndex, this.belowImageData = belowImageData; this.undo = function() { - console.log(afterAbove.menuEntry); canvasView.append(aboveLayer.canvas); layerList.insertBefore(aboveLayer.menuEntry, afterAbove); @@ -300,7 +313,6 @@ function HistoryStateAddLayer(layerData, index) { this.index = index; this.undo = function() { - console.log("uo"); redoStates.push(this); if (layers.length - nAppLayers > this.index + 1) { @@ -473,8 +485,6 @@ function saveHistoryState (state) { } function undo () { - //console.log('%cundo', undoLogStyle); - //if there are any states saved to undo if (undoStates.length > 0) { document.getElementById('redo-button').classList.remove('disabled'); @@ -496,8 +506,6 @@ function undo () { } function redo () { - //console.log('%credo', undoLogStyle); - if (redoStates.length > 0) { //enable undo button diff --git a/js/_hotkeyListener.js b/js/_hotkeyListener.js index 73ea4bc..ecb74b8 100644 --- a/js/_hotkeyListener.js +++ b/js/_hotkeyListener.js @@ -1,5 +1,9 @@ var spacePressed = false; +/** Just listens to hotkeys and calls the linked functions + * + * @param {*} e + */ function KeyPress(e) { var keyboardEvent = window.event? event : e; @@ -46,6 +50,9 @@ function KeyPress(e) { case 52: case 80: tool.pan.switchTo(); break; + case 76: + tool.line.switchTo(); + break; //zoom - 5 case 53: tool.zoom.switchTo(); @@ -58,6 +65,15 @@ function KeyPress(e) { case 77: case 109: tool.rectselect.switchTo() break; + // TODO: [ELLIPSE] Decide on a shortcut to use. "s" was chosen without any in-team consultation. + // ellipse tool, s + case 83: + tool.ellipse.switchTo() + break; + // rectangle tool, u + case 85: + tool.rectangle.switchTo() + break; // Paste tool case 86: case 118: if (keyboardEvent.ctrlKey && !dragging) { diff --git a/js/_initColor.js b/js/_initColor.js index 955abf4..c1bcf60 100644 --- a/js/_initColor.js +++ b/js/_initColor.js @@ -1,5 +1,7 @@ +// NEXTPULL: to remove when the new palette system is added -//format a color button + +//formats a color button function initColor (colorElement) { //console.log('initColor()'); //console.log(document.getElementById('jscolor-hex-input')) diff --git a/js/_jscolor.js b/js/_jscolor.js index e6c8c3b..2cbf910 100644 --- a/js/_jscolor.js +++ b/js/_jscolor.js @@ -1,3 +1,5 @@ +// NEXTPULL: to remove when the new palette system is added + /** * jscolor - JavaScript Color Picker * diff --git a/js/_layer.js b/js/_layer.js index 3500c43..d18a8e8 100644 --- a/js/_layer.js +++ b/js/_layer.js @@ -1,31 +1,29 @@ -/** TODO LIST FOR LAYERS - - GENERAL REQUIREMENTS: - - Saving the state of an artwork to a .lospec file so that people can work on it later keeping - the layers they created? That'd be cool, even for the app users, that could just double click on a lospec - file for it to be opened right in the pixel editor - - OPTIONAL: - - 1 - Fix issues -*/ - +// HTML element that contains the layer entries let layerList; +// A single layer entry (used as a prototype to create the new ones) let layerListEntry; - +// NEXTPULL: remove the drag n drop system and use Sortable.js instead let layerDragSource = null; +// Number of layers at the beginning let layerCount = 1; +// Current max z index (so that I know which z-index to assign to new layers) let maxZIndex = 3; +// When a layer is deleted, its id is added to this array and can be reused let unusedIDs = []; +// Id for the next added layer let currentID = layerCount; -let idToDelete; +// Layer menu let layerOptions = document.getElementById("layer-properties-menu"); - +// Is the user currently renaming a layer? let isRenamingLayer = false; +// I need to save this, trust me let oldLayerName = null; +let dragStartLayer; + +// Binding the add layer button to the function on('click',"add-layer-button", addLayer, false); /** Handler class for a single canvas (a single layer) @@ -54,6 +52,7 @@ class Layer { this.id = "layer" + id; + // Binding the events if (menuEntry != null) { this.name = menuEntry.getElementsByTagName("p")[0].innerHTML; menuEntry.id = "layer" + id; @@ -78,20 +77,13 @@ class Layer { // Initializes the canvas initialize() { - /* - var maxHorizontalZoom = Math.floor(window.innerWidth/this.canvasSize[0]*0.75); - var maxVerticalZoom = Math.floor(window.innerHeight/this.canvasSize[1]*0.75); - - zoom = Math.min(maxHorizontalZoom,maxVerticalZoom); - if (zoom < 1) zoom = 1; - */ //resize canvas this.canvas.width = this.canvasSize[0]; this.canvas.height = this.canvasSize[1]; this.canvas.style.width = (this.canvas.width*zoom)+'px'; this.canvas.style.height = (this.canvas.height*zoom)+'px'; - //unhide canvas + //show canvas this.canvas.style.display = 'block'; //center canvas in window @@ -103,7 +95,7 @@ class Layer { } hover() { - // Hide all the layers but the current one + // Hides all the layers but the current one for (let i=1; i=0; i--) { getLayerByID(menuEntries[i].id).canvas.style.zIndex++; } - maxZIndex++; + maxZIndex+=2; // Creating a new canvas let newCanvas = document.createElement("canvas"); // Setting up the new canvas canvasView.append(newCanvas); - newCanvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex) + 1; + newCanvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex) + 2; newCanvas.classList.add("drawingCanvas"); if (!layerListEntry) return console.warn('skipping adding layer because no document'); @@ -545,78 +486,6 @@ function renameLayer(event) { isRenamingLayer = true; } -// Swaps two layer entries in the layer menu -function moveLayers(toDropLayer, staticLayer, saveHistory = true) { - let toDrop = getLayerByID(toDropLayer); - let staticc = getLayerByID(staticLayer); - let layerCopy = layers.slice(); - - let beforeToDrop = toDrop.menuEntry.nextElementSibling; - let nMoved = 0; - - layerCopy.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? 1 : -1); - - let toDropIndex = layerCopy.indexOf(toDrop); - let staticIndex = layerCopy.indexOf(staticc); - - layerList.insertBefore(toDrop.menuEntry, staticc.menuEntry); - - if (toDropIndex < staticIndex) { - let tmp = toDrop.canvas.style.zIndex; - let tmp2; - - for (let i=toDropIndex+1; i<=staticIndex; i++) { - tmp2 = layerCopy[i].canvas.style.zIndex; - - if (saveHistory) { - new HistoryStateMoveTwoLayers(layerCopy[i], tmp2, tmp); - } - - layerCopy[i].canvas.style.zIndex = tmp; - tmp = tmp2; - nMoved++; - } - - if (saveHistory) { - new HistoryStateMoveTwoLayers(toDrop, toDrop.canvas.style.zIndex, tmp); - } - - toDrop.canvas.style.zIndex = tmp; - - if (saveHistory) { - new HistoryStateMoveLayer(beforeToDrop, toDrop, staticc, nMoved); - } - } - else { - // BUG QUI - let tmp = toDrop.canvas.style.zIndex; - let tmp2; - - for (let i=toDropIndex-1; i>staticIndex; i--) { - tmp2 = layerCopy[i].canvas.style.zIndex; - - if (saveHistory) { - new HistoryStateMoveTwoLayers(layerCopy[i], tmp2, tmp); - } - - layerCopy[i].canvas.style.zIndex = tmp; - tmp = tmp2; - nMoved++; - } - - if (saveHistory) { - new HistoryStateMoveTwoLayers(toDrop, toDrop.canvas.style.zIndex, tmp); - } - - toDrop.canvas.style.zIndex = tmp; - - if (saveHistory) { - new HistoryStateMoveLayer(beforeToDrop, toDrop, staticc, nMoved); - } - - } -} - function getMenuEntryIndex(list, entry) { for (let i=0; i newIndex) + { + for (let i=newIndex; ioldIndex; i--) { + getLayerByID(layerList.children[i].id).canvas.style.zIndex = getLayerByID(layerList.children[i - 1].id).canvas.style.zIndex; + } + } + + getLayerByID(layerList.children[oldIndex].id).canvas.style.zIndex = movedZIndex; + + dragging = false; +} + +layerList = document.getElementById("layers-menu"); + +// Making the layers list sortable +new Sortable(document.getElementById("layers-menu"), { + animation: 100, + filter: ".layer-button", + draggable: ".layers-menu-entry", + onStart: layerDragStart, + onEnd: layerDragDrop +}); \ No newline at end of file diff --git a/js/_line.js b/js/_line.js new file mode 100644 index 0000000..aa9f828 --- /dev/null +++ b/js/_line.js @@ -0,0 +1,45 @@ +function diagLine(lastMouseClickPos, zoom, cursorLocation) { + + let x0 = Math.floor(lastMouseClickPos[0]/zoom); + let y0 = Math.floor(lastMouseClickPos[1]/zoom); + let x1 = Math.floor(cursorLocation[0]/zoom); + let y1 = Math.floor(cursorLocation[1]/zoom); + + let dx = Math.abs(x1-x0); + let dy = Math.abs(y1-y0); + let sx = (x0 < x1 ? 1 : -1); + let sy = (y0 < y1 ? 1 : -1); + let err = dx-dy; + + const brushSize = tool.line.brushSize; + + const canvas = document.getElementById('tmp-canvas'); + const context = canvas.getContext('2d'); + + context.fillStyle=currentGlobalColor; + context.clearRect(0, 0, canvas.width, canvas.height); + + canvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex, 10) + 1; + + //console.log(canvas.style.zIndex, currentLayer.canvas.style.zIndex); + + while (true) { + if (currentTool.name !== 'line') return; + + context.fillRect(x0-Math.floor(brushSize/2), y0-Math.floor(brushSize/2), brushSize, brushSize); + + //if we've reached the end goal, exit the loop + if ((x0==x1) && (y0==y1)) break; + var e2 = 2*err; + + if (e2 >-dy) { + err -=dy; + x0+=sx; + } + + if (e2 < dx) { + err +=dx; + y0+=sy; + } + } +} \ No newline at end of file diff --git a/js/_loadImage.js b/js/_loadImage.js index fccdd81..a9897b6 100644 --- a/js/_loadImage.js +++ b/js/_loadImage.js @@ -1,18 +1,27 @@ +/** Loads a file (.png or .lpe) + * + */ document.getElementById('open-image-browse-holder').addEventListener('change', function () { let fileName = document.getElementById("open-image-browse-holder").value; + // Getting the extension let extension = fileName.substring(fileName.lastIndexOf('.')+1, fileName.length) || fileName; + // I didn't write this check and I have no idea what it does if (this.files && this.files[0]) { + // Btw, checking if the extension is supported if (extension == 'png' || extension == 'gif' || extension == 'lpe') { + // If it's a Lospec Pixel Editor tm file, I load the project if (extension == 'lpe') { let file = this.files[0]; let reader = new FileReader(); + // Getting all the data reader.readAsText(file, "UTF-8"); + // Converting the data to a json object and creating a new pixel (see _newPixel.js for more) reader.onload = function (e) { let dictionary = JSON.parse(e.target.result); - - newPixel(dictionary['canvasWidth'], dictionary['canvasHeight'], dictionary['editorMode'], dictionary); + let mode = dictionary['editorMode'] == 'Advanced' ? 'Basic' : 'Advanced'; + newPixel(dictionary['canvasWidth'], dictionary['canvasHeight'], mode, dictionary); } } else { diff --git a/js/_loadPalette.js b/js/_loadPalette.js index a12d2b5..035bd2a 100644 --- a/js/_loadPalette.js +++ b/js/_loadPalette.js @@ -1,8 +1,8 @@ //this is called when a user picks a file after selecting "load palette" from the new pixel dialogue +// TODO: load palette from .lpe file document.getElementById('load-palette-browse-holder').addEventListener('change', function () { if (this.files && this.files[0]) { - //make sure file is allowed filetype var fileContentType = this.files[0].type; if (fileContentType == 'image/png' || fileContentType == 'image/gif') { @@ -26,8 +26,6 @@ document.getElementById('load-palette-browse-holder').addEventListener('change', var colorPalette = []; var imagePixelData = loadPaletteContext.getImageData(0,0,this.width, this.height).data; - console.log(imagePixelData); - //loop through pixels looking for colors to add to palette for (var i = 0; i < imagePixelData.length; i += 4) { var color = '#'+rgbToHex(imagePixelData[i],imagePixelData[i + 1],imagePixelData[i + 2]); @@ -40,6 +38,8 @@ document.getElementById('load-palette-browse-holder').addEventListener('change', palettes['Loaded palette'] = {}; palettes['Loaded palette'].colors = colorPalette; setText('palette-button', 'Loaded palette'); + setText('palette-button-splash', 'Loaded palette'); + toggle('palette-menu-splash'); }; img.src = e.target.result; }; diff --git a/js/_logs.js b/js/_logs.js new file mode 100644 index 0000000..30ee743 --- /dev/null +++ b/js/_logs.js @@ -0,0 +1,15 @@ +var rawFile = new XMLHttpRequest(); +rawFile.open("GET", '/pixel-editor/latestLog.html', false); +rawFile.onreadystatechange = function () +{ + if(rawFile.readyState === 4) + { + if(rawFile.status === 200 || rawFile.status == 0) + { + var allText = rawFile.responseText; + + document.getElementById("latest-update").innerHTML = allText; + } + } +} +rawFile.send(null); \ No newline at end of file diff --git a/js/_mouseEvents.js b/js/_mouseEvents.js index 124b18a..82c5853 100644 --- a/js/_mouseEvents.js +++ b/js/_mouseEvents.js @@ -22,7 +22,7 @@ window.addEventListener("mousedown", function (mouseEvent) { else if (mouseEvent.altKey) currentTool = tool.eyedropper; else if (mouseEvent.target.className == 'drawingCanvas' && - (currentTool.name == 'pencil' || currentTool.name == 'eraser' || currentTool.name == 'rectangle')) + (currentTool.name == 'pencil' || currentTool.name == 'eraser' || currentTool.name == 'rectangle' || currentTool.name == 'ellipse' || currentTool.name === 'line')) new HistoryStateEditCanvas(); else if (currentTool.name == 'moveselection') { if (!cursorInSelectedArea() && @@ -30,14 +30,15 @@ window.addEventListener("mousedown", function (mouseEvent) { tool.pencil.switchTo(); canDraw = false; } - } - - currentTool.updateCursor(); + } if (!currentLayer.isLocked && !currentLayer.isVisible && canDraw) { draw(mouseEvent); } } + else if (mouseEvent.which == 2) { + currentTool = tool.pan; + } else if (currentTool.name == 'pencil' && mouseEvent.which == 3) { currentTool = tool.resizebrush; tool.pencil.previousBrushSize = tool.pencil.brushSize; @@ -46,10 +47,15 @@ window.addEventListener("mousedown", function (mouseEvent) { currentTool = tool.resizeeraser; tool.eraser.previousBrushSize = tool.eraser.brushSize; } + // TODO: [ELLIPSE] Do we need similar logic related to ellipse? else if (currentTool.name == 'rectangle' && mouseEvent.which == 3) { currentTool = tool.resizerectangle; tool.rectangle.previousBrushSize = tool.rectangle.brushSize; } + else if (currentTool.name == 'line' && mouseEvent.which == 3) { + currentTool = tool.resizeline; + tool.line.previousBrushSize = tool.line.brushSize; + } if (currentTool.name == 'eyedropper' && mouseEvent.target.className == 'drawingCanvas') eyedropperPreview.style.display = 'block'; @@ -70,6 +76,15 @@ window.addEventListener("mouseup", function (mouseEvent) { currentLayer.closeOptionsMenu(); } + // If the user finished placing down a line, clear the tmp canvas and copy the data to the current layer + if (currentTool.name === "line") { + const tmpCanvas = document.getElementById('tmp-canvas'); + currentLayer.context.drawImage(tmpCanvas, 0, 0); + + const tmpContext = tmpCanvas.getContext('2d'); + tmpContext.clearRect(0, 0, tmpCanvas.width, tmpCanvas.height); + } + if (!documentCreated || dialogueOpen || !currentLayer.isVisible || currentLayer.isLocked) return; if (currentTool.name == 'eyedropper' && mouseEvent.target.className == 'drawingCanvas') { @@ -134,7 +149,7 @@ window.addEventListener("mouseup", function (mouseEvent) { mode = 'out'; } - changeZoom(layers[0], mode, getCursorPosition(mouseEvent)); + changeZoom(mode, getCursorPosition(mouseEvent)); for (let i=1; i endIndex) { + let tmp = startIndex; + startIndex = endIndex; + endIndex = tmp; + } + + for (let i=startIndex; i<=endIndex; i++) { + coloursList.removeChild(coloursList.children[startIndex]); + } + clearBorders(); +} + +/** Starts selecting a ramp. Saves the data needed to draw the outline. + * + * @param {*} mouseEvent + */ +function startRampSelection(mouseEvent) { + if (mouseEvent.which == 3) { + let index = getElementIndex(mouseEvent.target); + + isRampSelecting = true; + + currentSelection.startIndex = index; + currentSelection.endIndex = index; + + currentSelection.startCoords = getColourCoordinates(index); + currentSelection.endCoords = getColourCoordinates(index); + } +} + +/** Updates the outline for the current selection. + * + * @param {*} mouseEvent + */ +function updateRampSelection(mouseEvent) { + if (mouseEvent != null && mouseEvent.which == 3) { + currentSelection.endIndex = getElementIndex(mouseEvent.target); + } + + if (mouseEvent == null || mouseEvent.which == 3) { + let startCoords = getColourCoordinates(currentSelection.startIndex); + let endCoords = getColourCoordinates(currentSelection.endIndex); + + let startIndex = currentSelection.startIndex; + let endIndex = currentSelection.endIndex; + + if (currentSelection.startIndex > endIndex) { + let tmp = startIndex; + startIndex = endIndex; + endIndex = tmp; + + tmp = startCoords; + startCoords = endCoords; + endCoords = tmp; + } + + clearBorders(); + + for (let i=startIndex; i<=endIndex; i++) { + let currentSquare = coloursList.children[i]; + let currentCoords = getColourCoordinates(i); + let borderStyle = "3px solid white"; + let bordersToSet = []; + + // Deciding which borders to use to make the outline + if (i == 0 || i == startIndex) { + bordersToSet.push("border-left"); + } + if (currentCoords[1] == startCoords[1] || ((currentCoords[1] == startCoords[1] + 1)) && currentCoords[0] < startCoords[0]) { + bordersToSet.push("border-top"); + } + if (currentCoords[1] == endCoords[1] || ((currentCoords[1] == endCoords[1] - 1)) && currentCoords[0] > endCoords[0]) { + bordersToSet.push("border-bottom"); + } + if ((i == coloursList.childElementCount - 1) || (currentCoords[0] == Math.floor(blockData.blockWidth / blockData.squareSize) - 1) + || i == endIndex) { + bordersToSet.push("border-right"); + } + if (bordersToSet != []) { + currentSquare.style["box-sizing"] = "border-box"; + + for (let i=0; i 0 ? -5 : 5; + currentSquareSize += amount; + + for (let i=0; i { + const palettesMenu = document.getElementById('palette-menu'); + const splashPalettes = document.getElementById('palette-menu-splash'); + const noPaletteButton = document.getElementById('no-palette-button'); + const newPixelElement = document.getElementById('new-pixel'); + const paletteButton = document.getElementById('palette-button'); + const paletteButtonSplash = document.getElementById('palette-button-splash'); + const loadPaletteButton = document.getElementById('load-palette-button'); + const loadPaletteButtonSplash = document.getElementById('load-palette-button-splash'); - var palettesMenu = document.getElementById('palette-menu'); + Object.keys(palettes).forEach((paletteName,) => { - //create button - var button = document.createElement('button'); - button.appendChild(document.createTextNode(paletteName)); + const button = document.createElement('button'); + button.appendChild(document.createTextNode(paletteName)); - //insert new element - palettesMenu.appendChild(button); + //if the palette was specified by the user, change the dropdown to it + if (palettes[paletteName].specified) { + Util.setText('palette-button', paletteName); + Util.setText('palette-button-splash', paletteName) + //Show empty palette option + noPaletteButton.style.display = 'block'; + } - //if the palette was specified by the user, change the dropdown to it - if (palettes[paletteName].specified == true) { - setText('palette-button', paletteName); - //Show empty palette option - document.getElementById('no-palette-button').style.display = 'block'; - } + const buttonEvent = () => { + //hide the dropdown menu + Util.deselect('palette-menu'); + Util.deselect('palette-button'); + Util.deselect('palette-menu-splash'); + Util.deselect('palette-button-splash'); - on('click', button, function() { + //show empty palette option + noPaletteButton.style.display = 'block'; - //hide the dropdown menu - deselect('palette-menu'); - deselect('palette-button'); + //set the text of the dropdown to the newly selected preset + Util.setText('palette-button', paletteName); + Util.setText('palette-button-splash', paletteName); + } - //show empty palette option - document.getElementById('no-palette-button').style.display = 'block'; + // Making a copy for the splash page too + const copyButton = button.cloneNode(true); + copyButton.addEventListener('click', buttonEvent); + button.addEventListener('click', buttonEvent); - //set the text of the dropdown to the newly selected preset - setText('palette-button', paletteName); + // Appending it to the splash palette menu + splashPalettes.appendChild(copyButton); + palettesMenu.appendChild(button); }); -}); - -//select no palette -on('click', 'no-palette-button', function () { - document.getElementById('no-palette-button').style.display = 'none'; - setText('palette-button', 'Choose a palette...'); -}); - -//select load palette -on('click', 'load-palette-button', function () { - document.getElementById('load-palette-browse-holder').click(); -}); + const loadPaletteButtonEvent = () => { + document.getElementById('load-palette-browse-holder').click(); + } + const clickPaletteButtonEvent = (e) => { + Util.toggle('palette-button'); + Util.toggle('palette-menu'); -on('click', 'palette-button', function (e){ - toggle('palette-button'); - toggle('palette-menu'); + Util.deselect('preset-button'); + Util.deselect('preset-menu'); - deselect('preset-button'); - deselect('preset-menu'); - e.stopPropagation(); -}); + // Splash version + Util.toggle('palette-button-splash'); + Util.toggle('palette-menu-splash'); -on('click', 'new-pixel', function (){ - deselect('editor-mode-menu'); - deselect('preset-button'); - deselect('preset-menu'); - deselect('palette-button'); - deselect('palette-menu'); -}); + e.stopPropagation(); + } + // Load Palettes + loadPaletteButton.addEventListener('click', loadPaletteButtonEvent); + loadPaletteButtonSplash.addEventListener('click', loadPaletteButtonEvent); + + // Palette menu click + paletteButtonSplash.addEventListener('click', clickPaletteButtonEvent); + paletteButton.addEventListener('click', clickPaletteButtonEvent); + + noPaletteButton.addEventListener('click', () => { + noPaletteButton.style.display = 'none'; + Util.setText('palette-button', 'Choose a palette...'); + }) + + newPixelElement.addEventListener('click', () => { + Util.deselect('editor-mode-menu'); + Util.deselect('preset-button'); + Util.deselect('preset-menu'); + Util.deselect('palette-button'); + Util.deselect('palette-menu'); + + // Splash version + Util.deselect('palette-button-splash'); + Util.deselect('palette-menu-splash'); + }) +})(); \ No newline at end of file diff --git a/js/_pixelEditorUtility.js b/js/_pixelEditorUtility.js index 2708664..73fa2b8 100644 --- a/js/_pixelEditorUtility.js +++ b/js/_pixelEditorUtility.js @@ -1,3 +1,10 @@ +/***********MISCELLANEOUS UTILITY FUNCTIONS**************/ + +/** Merges topLayer onto belowLayer + * + * @param {*} belowLayer The layer on the bottom of the layer stack + * @param {*} topLayer The layer on the top of the layer stack + */ function mergeLayers(belowLayer, topLayer) { // Copying the above content on the layerBelow let belowImageData = belowLayer.getImageData(0, 0, canvas.width, canvas.height); @@ -24,10 +31,19 @@ function mergeLayers(belowLayer, topLayer) { } } + // Putting the top data into the belowdata belowLayer.putImageData(toMergeImageData, 0, 0); } +/** Used to programmatically create an input event + * + * @param {*} keyCode KeyCode of the key to press + * @param {*} ctrl Is ctrl pressed? + * @param {*} alt Is alt pressed? + * @param {*} shift Is shift pressed? + */ function simulateInput(keyCode, ctrl, alt, shift) { + // I just copy pasted this from stack overflow lol please have mercy let keyboardEvent = document.createEvent("KeyboardEvent"); let initMethod = typeof keyboardEvent.initKeyboardEvent !== 'undefined' ? "initKeyboardEvent" : "initKeyEvent"; @@ -46,38 +62,62 @@ function simulateInput(keyCode, ctrl, alt, shift) { document.dispatchEvent(keyboardEvent); } +/** Tells if a pixel is empty (has alpha = 0) + * + * @param {*} pixel + */ function isPixelEmpty(pixel) { if (pixel == null || pixel === undefined) { return false; } - if ((pixel[0] == 0 && pixel[1] == 0 && pixel[2] == 0) || pixel[3] == 0) { + // If the alpha channel is 0, the current pixel is empty + if (pixel[3] == 0) { return true; } return false; } +/** Tells if element is a child of an element with class className + * + * @param {*} element + * @param {*} className + */ function isChildOfByClass(element, className) { + // Getting the element with class className while (element != null && element.classList != null && !element.classList.contains(className)) { element = element.parentElement; } + // If that element exists and its class is the correct one if (element != null && element.classList != null && element.classList.contains(className)) { + // Then element is a chld of an element with class className return true; } return false; } +/** Gets the eyedropped colour (the colour of the pixel pointed by the cursor when the user is using the eyedropper). + * It takes the colour of the canvas with the biggest z-index, basically the one the user can see, since it doesn't + * make much sense to sample a colour which is hidden behind a different layer + * + * @param {*} cursorLocation The position of the cursor + */ function getEyedropperColor(cursorLocation) { + // Making sure max will take some kind of value let max = -1; + // Using tmpColour to sample the sprite let tmpColour; + // Returned colour let selectedColor; for (let i=1; i max || isPixelEmpty(selectedColor) || selectedColor === undefined) { max = layers[i].canvas.style.zIndex; @@ -87,6 +127,7 @@ function getEyedropperColor(cursorLocation) { } } + // If the final colour was empty, I return black if (isPixelEmpty(tmpColour) && selectedColor === undefined) { selectedColor = [0, 0, 0]; } @@ -94,7 +135,12 @@ function getEyedropperColor(cursorLocation) { return selectedColor; } +/** Gets the absolute position of the element (position on the screen) + * + * @param {*} element The element of which we have to get the position + */ function getElementAbsolutePosition(element) { + // Probably copy pasted this from stack overflow too, if not I don't recall how it works let curleft = curtop = 0; if (element.offsetParent) { @@ -107,9 +153,15 @@ function getElementAbsolutePosition(element) { return [curleft,curtop]; } +/** Nearest neighbor algorithm to scale a sprite + * + * @param {*} src The source imageData + * @param {*} dst The destination imageData + */ function nearestNeighbor (src, dst) { let pos = 0 + // Just applying the nearest neighbor algorithm for (let y = 0; y < dst.height; y++) { for (let x = 0; x < dst.width; x++) { const srcX = Math.floor(x * src.width / dst.width) @@ -125,7 +177,14 @@ function nearestNeighbor (src, dst) { } } +/** Bilinear interpolation used to scale a sprite + * + * @param {*} src The source imageData + * @param {*} dst The destination imageData + */ function bilinearInterpolation (src, dst) { + // Applying the bilinear interpolation algorithm + function interpolate (k, kMin, kMax, vMin, vMax) { return Math.round((k - kMin) * vMax + (kMax - k) * vMin) } @@ -167,14 +226,21 @@ function bilinearInterpolation (src, dst) { } } +/** Resizes an imageData depending on the algorithm and on the new width and height + * + * @param {*} image The imageData to scale + * @param {*} width The new width of the imageData + * @param {*} height The new height of the imageData + * @param {*} algorithm Scaling algorithm chosen by the user in the dialogue + */ function resizeImageData (image, width, height, algorithm) { algorithm = algorithm || 'bilinear-interpolation' - let resize + let resize; switch (algorithm) { case 'nearest-neighbor': resize = nearestNeighbor; break case 'bilinear-interpolation': resize = bilinearInterpolation; break - default: throw new Error(`Unknown algorithm: ${algorithm}`) + default: return image; } const result = new ImageData(width, height) @@ -184,10 +250,21 @@ function resizeImageData (image, width, height, algorithm) { return result } +/** Gets the position in (x, y) format of the pixel with index "index" + * + * @param {*} index The index of the pixel of which we need the (x, y) position + */ function getPixelPosition(index) { let linearIndex = index / 4; let x = linearIndex % layers[0].canvasSize[0]; let y = Math.floor(linearIndex / layers[0].canvasSize[0]); return [Math.ceil(x), Math.ceil(y)]; +} + +/** Sets isDragging to false, used when the user interacts with sortable lists + * + */ +function makeIsDraggingFalse(event) { + dragging = false; } \ No newline at end of file diff --git a/js/_pixelGrid.js b/js/_pixelGrid.js index 0c36c98..0ce55ce 100644 --- a/js/_pixelGrid.js +++ b/js/_pixelGrid.js @@ -1,27 +1,44 @@ +// Start colour of the pixel grid (can be changed in the preferences) let pixelGridColor = "#0000FF"; +// Distance between one line and another in HTML pixels let lineDistance = 12; +// The grid is not visible at the beginning let pixelGridVisible = false; +// Saving the canvas containing the pixel grid pixelGridCanvas = document.getElementById("pixel-grid"); +/** Shows or hides the pixel grid depening on its current visibility + * (triggered by the show pixel grid button in the top menu) + * + */ function togglePixelGrid(event) { + // Getting the button because I have to change its text let button = document.getElementById("toggle-pixelgrid-button"); + // Toggling the state pixelGridVisible = !pixelGridVisible; + // If it was visible, I hide it if (pixelGridVisible) { button.innerHTML = "Hide pixel grid"; pixelGridCanvas.style.display = "inline-block"; } + // Otherwise, I show it else { button.innerHTML = "Show pixel grid"; pixelGridCanvas.style.display = "none"; } } +/** Fills the pixelGridCanvas with the pixelgrid + * + */ function fillPixelGrid() { let context = pixelGridCanvas.getContext("2d"); let originalSize = layers[0].canvasSize; + // The pixelGridCanvas is lineDistance times bigger so that the lines don't take 1 canvas pixel + // (which would cover the whole canvas with the pixelGridColour), but they take 1/lineDistance canvas pixels pixelGridCanvas.width = originalSize[0] * lineDistance; pixelGridCanvas.height = originalSize[1] * lineDistance; diff --git a/js/_presets.js b/js/_presets.js index c0f4714..955f243 100644 --- a/js/_presets.js +++ b/js/_presets.js @@ -1,63 +1,60 @@ -//prests -var presets = { - 'Gameboy Color': { - width: 240, - height: 203, - palette: 'Gameboy Color' - }, - 'PICO-8': { - width: 128, - height: 128, - palette: 'PICO-8' - }, - 'Commodore 64': { - width: 40, - height: 80, - palette: 'Commodore 64' +const PresetModule = (() => { + const presets = { + 'Gameboy Color': {width: 240, height: 203, palette: 'Gameboy Color'}, + 'PICO-8': {width: 128, height: 128, palette: 'PICO-8'}, + 'Commodore 64': {width: 40, height: 80, palette: 'Commodore 64'} + }; + + function instrumentPresetMenu() { + console.info("Initializing presets.."); + // Add a button for all the presets available + const presetsMenu = document.getElementById('preset-menu'); + Object.keys(presets).forEach((presetName,) => { + + const button = document.createElement('button'); + button.appendChild(document.createTextNode(presetName)); + + presetsMenu.appendChild(button); + + button.addEventListener('click', () => { + //change dimentions on new pixel form + Util.setValue('size-width', presets[presetName].width); + Util.setValue('size-height', presets[presetName].height); + + //set the text of the dropdown to the newly selected preset + Util.setText('palette-button', presets[presetName].palette); + + //hide the dropdown menu + Util.deselect('preset-menu'); + Util.deselect('preset-button'); + + //set the text of the dropdown to the newly selected preset + Util.setText('preset-button', presetName); + + }); + }); + + const presetButton = document.getElementById('preset-button'); + presetButton.addEventListener('click', (e) => { + //open or close the preset menu + Util.toggle('preset-button'); + Util.toggle('preset-menu'); + + //close the palette menu + Util.deselect('palette-button'); + Util.deselect('palette-menu'); + + e.stopPropagation(); + }); } -}; -//populate preset list in new pixel menu -Object.keys(presets).forEach(function(presetName,index) { + function propertiesOf(presetId) { + return presets[presetId]; + } - var presetsMenu = document.getElementById('preset-menu'); - - //create button - var button = document.createElement('button'); - button.appendChild(document.createTextNode(presetName)); - - //insert new element - presetsMenu.appendChild(button); - - //add click event listener - on('click', button, function() { - - //change dimentions on new pixel form - setValue('size-width', presets[presetName].width); - setValue('size-height', presets[presetName].height); - - //set the text of the dropdown to the newly selected preset - setText('palette-button', presets[presetName].palette); - - //hide the dropdown menu - deselect('preset-menu'); - deselect('preset-button'); - - //set the text of the dropdown to the newly selected preset - setText('preset-button', presetName); - }); - -}); - -on('click', 'preset-button', function (e){ - //open or close the preset menu - toggle('preset-button'); - toggle('preset-menu'); - - //close the palette menu - deselect('palette-button'); - deselect('palette-menu'); - - //stop the click from propogating to the parent element - e.stopPropagation(); -}); + return { + propertiesOf, + instrumentPresetMenu + }; + +})(); \ No newline at end of file diff --git a/js/_rectSelect.js b/js/_rectSelect.js index a2705cc..ade0ba1 100644 --- a/js/_rectSelect.js +++ b/js/_rectSelect.js @@ -4,6 +4,10 @@ let startY; let endX; let endY; +/** Starts the selection: saves the canvas, sets the start coordinates + * + * @param {*} mouseEvent + */ function startRectSelection(mouseEvent) { // Saving the canvas new HistoryStateEditCanvas(); @@ -35,6 +39,10 @@ function startRectSelection(mouseEvent) { selectionCanceled = false; } +/** Updates the selection + * + * @param {*} mouseEvent + */ function updateRectSelection(mouseEvent) { let pos = getCursorPosition(mouseEvent); @@ -42,6 +50,10 @@ function updateRectSelection(mouseEvent) { drawRect(Math.round(pos[0] / zoom) + 0.5, Math.round(pos[1] / zoom) + 0.5); } +/** Ends the selection: sets the end coordiantes + * + * @param {*} mouseEvent + */ function endRectSelection(mouseEvent) { // Getting the end position let currentPos = getCursorPosition(mouseEvent); @@ -72,6 +84,10 @@ function endRectSelection(mouseEvent) { currentTool.updateCursor(); } +/** Cuts the selection from its canvas and puts it in the tmp layer so it can be moved + * + * @param {*} mousePosition + */ function cutSelection(mousePosition) { // Getting the selected pixels imageDataToMove = currentLayer.context.getImageData(startX, startY, endX - startX + 1, endY - startY + 1); @@ -79,10 +95,13 @@ function cutSelection(mousePosition) { currentLayer.context.clearRect(startX - 0.5, startY - 0.5, endX - startX + 1, endY - startY + 1); // Moving those pixels from the current layer to the tmp layer TMPLayer.context.putImageData(imageDataToMove, startX + 1, startY); - - //originalDataPosition = [currentPos[0], currentPos[1]]; } +/** Draws a dashed rectangle representing the selection + * + * @param {*} x Current end x coordinate of the selection + * @param {*} y Current end y coordinate of the selection + */ function drawRect(x, y) { // Getting the vfx context let vfxContext = VFXCanvas.getContext('2d'); @@ -106,10 +125,13 @@ function applyChanges() { // Checks whether the pointer is inside the selected area or not function cursorInSelectedArea() { + // Getting the cursor position let cursorPos = getCursorPosition(currentMouseEvent); + // Getting the coordinates relatively to the canvas let x = cursorPos[0] / zoom; let y = cursorPos[1] / zoom; + // This is to avoid rightX or topY being less than leftX or bottomY let leftX = Math.min(startX, endX); let rightX = Math.max(startX, endX); let topY = Math.max(startY, endY); @@ -126,6 +148,13 @@ function cursorInSelectedArea() { return false; } +/** Moves the rect ants to the specified position + * + * @param {*} x X coordinate of the rect ants + * @param {*} y Y coordinat of the rect ants + * @param {*} width Width of the selection + * @param {*} height Height of the selectin + */ function moveSelection(x, y, width, height) { // Getting the vfx context let vfxContext = VFXCanvas.getContext('2d'); @@ -135,6 +164,7 @@ function moveSelection(x, y, width, height) { vfxContext.lineWidth = 1; vfxContext.setLineDash([4]); + // Fixing the coordinates startX = Math.round(Math.round(x) - 0.5 - Math.round(width / 2)) + 0.5; startY = Math.round(Math.round(y) - 0.5 - Math.round(height / 2)) + 0.5; endX = startX + Math.round(width); diff --git a/js/_rectangle.js b/js/_rectangle.js index 516f730..f4ab4c5 100644 --- a/js/_rectangle.js +++ b/js/_rectangle.js @@ -1,19 +1,26 @@ +// Saving the empty rect svg +var emptyRectangleSVG = document.getElementById("rectangle-empty-button-svg"); +// and the full rect svg so that I can change them when the user changes rect modes +var fullRectangleSVG = document.getElementById("rectangle-full-button-svg"); -var emptySVG = document.getElementById("empty-button-svg"); -var fullSVG = document.getElementById("full-button-svg"); - -var drawMode = 'empty'; +// The start mode is empty rectangle +var rectangleDrawMode = 'empty'; +// I'm not drawing a rectangle at the beginning var isDrawingRect = false; +// Rect coordinates let startRectX; let startRectY; let endRectX; let endRectY; - +/** Starts drawing the rect, saves the start coordinates + * + * @param {*} mouseEvent + */ function startRectDrawing(mouseEvent) { - // Putting the vfx layer on top of everything - VFXCanvas.style.zIndex = MAX_Z_INDEX; + // Putting the vfx layer on top of everything + VFXCanvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex, 10) + 1;; // Updating flag isDrawingRect = true; @@ -25,20 +32,29 @@ function startRectDrawing(mouseEvent) { drawRectangle(startRectX, startRectY); } +/** Updates the rectangle preview depending on the position of the mouse + * + * @param {*} mouseEvent The mouseEvent from which we'll get the mouse position + */ function updateRectDrawing(mouseEvent) { let pos = getCursorPosition(mouseEvent); - // Drawing the rect - drawRectangle(Math.round(pos[0] / zoom) + 0.5, Math.round(pos[1] / zoom) + 0.5); + // Drawing the rect at the right position + drawRectangle(Math.floor(pos[0] / zoom) + 0.5, Math.floor(pos[1] / zoom) + 0.5); } +/** Finishes drawing the rect, decides the end coordinates and moves the preview rectangle to the + * current layer + * + * @param {*} mouseEvent event from which we'll get the mouse position + */ function endRectDrawing(mouseEvent) { // Getting the end position let currentPos = getCursorPosition(mouseEvent); let vfxContext = VFXCanvas.getContext("2d"); - endRectX = Math.round(currentPos[0] / zoom) + 0.5; - endRectY = Math.round(currentPos[1] / zoom) + 0.5; + endRectX = Math.floor(currentPos[0] / zoom) + 0.5; + endRectY = Math.floor(currentPos[1] / zoom) + 0.5; // Inverting end and start (start must always be the top left corner) if (endRectX < startRectX) { @@ -61,15 +77,18 @@ function endRectDrawing(mouseEvent) { endRectX -= 0.5; startRectX -= 0.5; + // Setting the correct linewidth and colour currentLayer.context.lineWidth = tool.rectangle.brushSize; currentLayer.context.fillStyle = currentGlobalColor; + // Drawing the rect using 4 lines line(startRectX, startRectY, endRectX, startRectY, tool.rectangle.brushSize); line(endRectX, startRectY, endRectX, endRectY, tool.rectangle.brushSize); line(endRectX, endRectY, startRectX, endRectY, tool.rectangle.brushSize); line(startRectX, endRectY, startRectX, startRectY, tool.rectangle.brushSize); - if (drawMode == 'fill') { + // If I have to fill it, I do so + if (rectangleDrawMode == 'fill') { currentLayer.context.fillRect(startRectX, startRectY, endRectX - startRectX, endRectY - startRectY); } @@ -77,6 +96,12 @@ function endRectDrawing(mouseEvent) { vfxContext.clearRect(0, 0, VFXCanvas.width, VFXCanvas.height); } +/** Draws a rectangle with end coordinates given by x and y on the VFX layer (draws + * the preview for the rectangle tool) + * + * @param {*} x The current end x of the rectangle + * @param {*} y The current end y of the rectangle + */ function drawRectangle(x, y) { // Getting the vfx context let vfxContext = VFXCanvas.getContext("2d"); @@ -98,21 +123,23 @@ function drawRectangle(x, y) { } vfxContext.setLineDash([]); - vfxContext.stroke(); } +/** Sets the correct tool icon depending on its mode + * + */ function setRectToolSvg() { - if (drawMode == 'empty') { - emptySVG.setAttribute('display', 'visible'); - fullSVG.setAttribute('display', 'none'); + if (rectangleDrawMode == 'empty') { + emptyRectangleSVG.setAttribute('display', 'visible'); + fullRectangleSVG.setAttribute('display', 'none'); } else { - emptySVG.setAttribute('display', 'none'); - fullSVG.setAttribute('display', 'visible'); + emptyRectangleSVG.setAttribute('display', 'none'); + fullRectangleSVG.setAttribute('display', 'visible'); } } function applyChanges() { - VFXCanvas.style.zIndex = MIN_Z_INDEX; + //VFXCanvas.style.zIndex = MIN_Z_INDEX; } diff --git a/js/_resizeCanvas.js b/js/_resizeCanvas.js index 42c4aa0..faa6641 100644 --- a/js/_resizeCanvas.js +++ b/js/_resizeCanvas.js @@ -1,16 +1,31 @@ -let resizeCanvasContainer = document.getElementById("resize-canvas"); -let rcPivot = "middle"; -let currentPivotObject; -let borders = {left: 0, right: 0, top: 0, bottom: 0}; +/* This scripts contains all the code used to handle the canvas resizing */ +// Resize canvas pop up window +let resizeCanvasContainer = document.getElementById("resize-canvas"); +// Start pivot +let rcPivot = "middle"; +// Selected pivot button +let currentPivotObject; +// Border offsets +let rcBorders = {left: 0, right: 0, top: 0, bottom: 0}; + +/** Opens the canvas resize window + * + */ function openResizeCanvasWindow() { + // Initializes the inputs initResizeCanvasInputs(); showDialogue('resize-canvas'); } +/** Initializes the canvas resizing input + * + */ function initResizeCanvasInputs() { + // Getting the pivot buttons let buttons = document.getElementsByClassName("pivot-button"); + // Adding the event handlers for them for (let i=0; i 1) + tool.ellipse.brushSize--; +}, false); + //fill on('click',"fill-button", function(){ tool.fill.switchTo(); @@ -75,33 +104,24 @@ on('click',"eyedropper-button", function(){ tool.eyedropper.switchTo(); }, false); -//zoom tool button -on('click',"zoom-button", function(){ - tool.zoom.switchTo(); -}, false); - -//zoom in button -on('click','zoom-in-button', function(){ - //changeZoom('in',[window.innerWidth/2-canvas.offsetLeft,window.innerHeight/2-canvas.offsetTop]); - changeZoom(layers[0],'in', [canvasSize[0] * zoom / 2, canvasSize[1] * zoom / 2]); - - for (let i=1; i 1) + tool.line.brushSize--; +}, false); + + /*global on */ diff --git a/js/_tools.js b/js/_tools.js index 3c10d70..a6d374d 100644 --- a/js/_tools.js +++ b/js/_tools.js @@ -95,15 +95,14 @@ class Tool { } moveBrushPreview(cursorLocation) { - let toSub = 0; - // Prevents the brush to be put in the middle of pixels - if (this.currentBrushSize % 2 == 0) { - toSub = 0.5; - } - - brushPreview.style.left = (Math.ceil(cursorLocation[0] / zoom) * zoom + currentLayer.canvas.offsetLeft - this.currentBrushSize * zoom / 2 - zoom / 2 - toSub * zoom) + 'px'; - brushPreview.style.top = (Math.ceil(cursorLocation[1] / zoom) * zoom + currentLayer.canvas.offsetTop - this.currentBrushSize * zoom / 2 - zoom / 2 - toSub * zoom) + 'px'; - } + let toSub = 1; + // Prevents the brush to be put in the middle of pixels + if (this.currentBrushSize % 2 == 0) { + toSub = 0.5; + } + brushPreview.style.left = (Math.floor(cursorLocation[0] / zoom) * zoom + currentLayer.canvas.offsetLeft - this.currentBrushSize * zoom / 2 - zoom / 2 + toSub * zoom) + 'px'; + brushPreview.style.top = (Math.floor(cursorLocation[1] / zoom) * zoom + currentLayer.canvas.offsetTop - this.currentBrushSize * zoom / 2 - zoom / 2 + toSub * zoom) + 'px'; + } } diff --git a/js/_variables.js b/js/_variables.js index b84b1a3..4c85ab9 100644 --- a/js/_variables.js +++ b/js/_variables.js @@ -5,7 +5,7 @@ var dragging = false; var lastMouseClickPos = [0,0]; var dialogueOpen = false; var documentCreated = false; -var pixelEditorMode; +var pixelEditorMode = "Advanced"; //common elements var brushPreview = document.getElementById("brush-preview"); diff --git a/js/pixel-editor-splash-page.js b/js/pixel-editor-splash-page.js index c671905..2d798d8 100644 --- a/js/pixel-editor-splash-page.js +++ b/js/pixel-editor-splash-page.js @@ -1,8 +1,6 @@ ajax('https://api.github.com/repos/lospec/pixel-editor/contributors', response => { - console.log(response) - if (Array.isArray(response)) { var html = ''; diff --git a/js/pixel-editor.js b/js/pixel-editor.js index 52b1f4b..137a9bf 100644 --- a/js/pixel-editor.js +++ b/js/pixel-editor.js @@ -12,6 +12,9 @@ //=include utilities/hslToRgb.js //=include libraries/cookies.js //=include _pixelEditorUtility.js +//=include sortable.js +//=include _algorithms.js +//=include Util.js /**init**/ //=include _consts.js @@ -28,16 +31,16 @@ //=include tools/*.js //=include _newPixel.js //=include _createColorPalette.js -//=include _setCanvasOffset.js //=include _changeZoom.js //=include _addColor.js //=include _colorChanged.js //=include _initColor.js //=include _dialogue.js -//=include _updateCursor.js +//=include _featuresLog.js //=include _drawLine.js //=include _getCursorPosition.js //=include _fill.js +//=include _line.js //=include _history.js //=include _deleteColor.js //=include _replaceAllOfColor.js @@ -47,6 +50,10 @@ //=include _copyPaste.js //=include _resizeCanvas.js //=include _resizeSprite.js +//=include _colorPicker.js +//=include _paletteBlock.js +//=include _splashPage.js +//=include _logs.js /**load file**/ //=include _loadImage.js @@ -65,6 +72,7 @@ //=include _rectSelect.js //=include _move.js //=include _rectangle.js +//=include _ellipse.js /**onload**/ //=include _onLoad.js @@ -72,3 +80,9 @@ /**libraries**/ //=include _jscolor.js + +/**feature toggles**/ +//=include _featureToggles.js + +// Controls execution of this preset module +PresetModule.instrumentPresetMenu(); \ No newline at end of file diff --git a/js/sortable.js b/js/sortable.js new file mode 100644 index 0000000..c6d7f9d --- /dev/null +++ b/js/sortable.js @@ -0,0 +1,3709 @@ +/**! + * Sortable 1.10.2 + * @author RubaXa + * @author owenm + * @license MIT + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.Sortable = factory()); +}(this, function () { 'use strict'; + + function _typeof(obj) { + if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { + _typeof = function (obj) { + return typeof obj; + }; + } else { + _typeof = function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + } + + return _typeof(obj); + } + + function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; + } + + function _extends() { + _extends = Object.assign || function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + + return target; + }; + + return _extends.apply(this, arguments); + } + + function _objectSpread(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] != null ? arguments[i] : {}; + var ownKeys = Object.keys(source); + + if (typeof Object.getOwnPropertySymbols === 'function') { + ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { + return Object.getOwnPropertyDescriptor(source, sym).enumerable; + })); + } + + ownKeys.forEach(function (key) { + _defineProperty(target, key, source[key]); + }); + } + + return target; + } + + function _objectWithoutPropertiesLoose(source, excluded) { + if (source == null) return {}; + var target = {}; + var sourceKeys = Object.keys(source); + var key, i; + + for (i = 0; i < sourceKeys.length; i++) { + key = sourceKeys[i]; + if (excluded.indexOf(key) >= 0) continue; + target[key] = source[key]; + } + + return target; + } + + function _objectWithoutProperties(source, excluded) { + if (source == null) return {}; + + var target = _objectWithoutPropertiesLoose(source, excluded); + + var key, i; + + if (Object.getOwnPropertySymbols) { + var sourceSymbolKeys = Object.getOwnPropertySymbols(source); + + for (i = 0; i < sourceSymbolKeys.length; i++) { + key = sourceSymbolKeys[i]; + if (excluded.indexOf(key) >= 0) continue; + if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; + target[key] = source[key]; + } + } + + return target; + } + + function _toConsumableArray(arr) { + return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); + } + + function _arrayWithoutHoles(arr) { + if (Array.isArray(arr)) { + for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; + + return arr2; + } + } + + function _iterableToArray(iter) { + if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); + } + + function _nonIterableSpread() { + throw new TypeError("Invalid attempt to spread non-iterable instance"); + } + + var version = "1.10.2"; + + function userAgent(pattern) { + if (typeof window !== 'undefined' && window.navigator) { + return !! + /*@__PURE__*/ + navigator.userAgent.match(pattern); + } + } + + var IE11OrLess = userAgent(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i); + var Edge = userAgent(/Edge/i); + var FireFox = userAgent(/firefox/i); + var Safari = userAgent(/safari/i) && !userAgent(/chrome/i) && !userAgent(/android/i); + var IOS = userAgent(/iP(ad|od|hone)/i); + var ChromeForAndroid = userAgent(/chrome/i) && userAgent(/android/i); + + var captureMode = { + capture: false, + passive: false + }; + + function on(el, event, fn) { + el.addEventListener(event, fn, !IE11OrLess && captureMode); + } + + function off(el, event, fn) { + el.removeEventListener(event, fn, !IE11OrLess && captureMode); + } + + function matches( + /**HTMLElement*/ + el, + /**String*/ + selector) { + if (!selector) return; + selector[0] === '>' && (selector = selector.substring(1)); + + if (el) { + try { + if (el.matches) { + return el.matches(selector); + } else if (el.msMatchesSelector) { + return el.msMatchesSelector(selector); + } else if (el.webkitMatchesSelector) { + return el.webkitMatchesSelector(selector); + } + } catch (_) { + return false; + } + } + + return false; + } + + function getParentOrHost(el) { + return el.host && el !== document && el.host.nodeType ? el.host : el.parentNode; + } + + function closest( + /**HTMLElement*/ + el, + /**String*/ + selector, + /**HTMLElement*/ + ctx, includeCTX) { + if (el) { + ctx = ctx || document; + + do { + if (selector != null && (selector[0] === '>' ? el.parentNode === ctx && matches(el, selector) : matches(el, selector)) || includeCTX && el === ctx) { + return el; + } + + if (el === ctx) break; + /* jshint boss:true */ + } while (el = getParentOrHost(el)); + } + + return null; + } + + var R_SPACE = /\s+/g; + + function toggleClass(el, name, state) { + if (el && name) { + if (el.classList) { + el.classList[state ? 'add' : 'remove'](name); + } else { + var className = (' ' + el.className + ' ').replace(R_SPACE, ' ').replace(' ' + name + ' ', ' '); + el.className = (className + (state ? ' ' + name : '')).replace(R_SPACE, ' '); + } + } + } + + function css(el, prop, val) { + var style = el && el.style; + + if (style) { + if (val === void 0) { + if (document.defaultView && document.defaultView.getComputedStyle) { + val = document.defaultView.getComputedStyle(el, ''); + } else if (el.currentStyle) { + val = el.currentStyle; + } + + return prop === void 0 ? val : val[prop]; + } else { + if (!(prop in style) && prop.indexOf('webkit') === -1) { + prop = '-webkit-' + prop; + } + + style[prop] = val + (typeof val === 'string' ? '' : 'px'); + } + } + } + + function matrix(el, selfOnly) { + var appliedTransforms = ''; + + if (typeof el === 'string') { + appliedTransforms = el; + } else { + do { + var transform = css(el, 'transform'); + + if (transform && transform !== 'none') { + appliedTransforms = transform + ' ' + appliedTransforms; + } + /* jshint boss:true */ + + } while (!selfOnly && (el = el.parentNode)); + } + + var matrixFn = window.DOMMatrix || window.WebKitCSSMatrix || window.CSSMatrix || window.MSCSSMatrix; + /*jshint -W056 */ + + return matrixFn && new matrixFn(appliedTransforms); + } + + function find(ctx, tagName, iterator) { + if (ctx) { + var list = ctx.getElementsByTagName(tagName), + i = 0, + n = list.length; + + if (iterator) { + for (; i < n; i++) { + iterator(list[i], i); + } + } + + return list; + } + + return []; + } + + function getWindowScrollingElement() { + var scrollingElement = document.scrollingElement; + + if (scrollingElement) { + return scrollingElement; + } else { + return document.documentElement; + } + } + /** + * Returns the "bounding client rect" of given element + * @param {HTMLElement} el The element whose boundingClientRect is wanted + * @param {[Boolean]} relativeToContainingBlock Whether the rect should be relative to the containing block of (including) the container + * @param {[Boolean]} relativeToNonStaticParent Whether the rect should be relative to the relative parent of (including) the contaienr + * @param {[Boolean]} undoScale Whether the container's scale() should be undone + * @param {[HTMLElement]} container The parent the element will be placed in + * @return {Object} The boundingClientRect of el, with specified adjustments + */ + + + function getRect(el, relativeToContainingBlock, relativeToNonStaticParent, undoScale, container) { + if (!el.getBoundingClientRect && el !== window) return; + var elRect, top, left, bottom, right, height, width; + + if (el !== window && el !== getWindowScrollingElement()) { + elRect = el.getBoundingClientRect(); + top = elRect.top; + left = elRect.left; + bottom = elRect.bottom; + right = elRect.right; + height = elRect.height; + width = elRect.width; + } else { + top = 0; + left = 0; + bottom = window.innerHeight; + right = window.innerWidth; + height = window.innerHeight; + width = window.innerWidth; + } + + if ((relativeToContainingBlock || relativeToNonStaticParent) && el !== window) { + // Adjust for translate() + container = container || el.parentNode; // solves #1123 (see: https://stackoverflow.com/a/37953806/6088312) + // Not needed on <= IE11 + + if (!IE11OrLess) { + do { + if (container && container.getBoundingClientRect && (css(container, 'transform') !== 'none' || relativeToNonStaticParent && css(container, 'position') !== 'static')) { + var containerRect = container.getBoundingClientRect(); // Set relative to edges of padding box of container + + top -= containerRect.top + parseInt(css(container, 'border-top-width')); + left -= containerRect.left + parseInt(css(container, 'border-left-width')); + bottom = top + elRect.height; + right = left + elRect.width; + break; + } + /* jshint boss:true */ + + } while (container = container.parentNode); + } + } + + if (undoScale && el !== window) { + // Adjust for scale() + var elMatrix = matrix(container || el), + scaleX = elMatrix && elMatrix.a, + scaleY = elMatrix && elMatrix.d; + + if (elMatrix) { + top /= scaleY; + left /= scaleX; + width /= scaleX; + height /= scaleY; + bottom = top + height; + right = left + width; + } + } + + return { + top: top, + left: left, + bottom: bottom, + right: right, + width: width, + height: height + }; + } + /** + * Checks if a side of an element is scrolled past a side of its parents + * @param {HTMLElement} el The element who's side being scrolled out of view is in question + * @param {String} elSide Side of the element in question ('top', 'left', 'right', 'bottom') + * @param {String} parentSide Side of the parent in question ('top', 'left', 'right', 'bottom') + * @return {HTMLElement} The parent scroll element that the el's side is scrolled past, or null if there is no such element + */ + + + function isScrolledPast(el, elSide, parentSide) { + var parent = getParentAutoScrollElement(el, true), + elSideVal = getRect(el)[elSide]; + /* jshint boss:true */ + + while (parent) { + var parentSideVal = getRect(parent)[parentSide], + visible = void 0; + + if (parentSide === 'top' || parentSide === 'left') { + visible = elSideVal >= parentSideVal; + } else { + visible = elSideVal <= parentSideVal; + } + + if (!visible) return parent; + if (parent === getWindowScrollingElement()) break; + parent = getParentAutoScrollElement(parent, false); + } + + return false; + } + /** + * Gets nth child of el, ignoring hidden children, sortable's elements (does not ignore clone if it's visible) + * and non-draggable elements + * @param {HTMLElement} el The parent element + * @param {Number} childNum The index of the child + * @param {Object} options Parent Sortable's options + * @return {HTMLElement} The child at index childNum, or null if not found + */ + + + function getChild(el, childNum, options) { + var currentChild = 0, + i = 0, + children = el.children; + + while (i < children.length) { + if (children[i].style.display !== 'none' && children[i] !== Sortable.ghost && children[i] !== Sortable.dragged && closest(children[i], options.draggable, el, false)) { + if (currentChild === childNum) { + return children[i]; + } + + currentChild++; + } + + i++; + } + + return null; + } + /** + * Gets the last child in the el, ignoring ghostEl or invisible elements (clones) + * @param {HTMLElement} el Parent element + * @param {selector} selector Any other elements that should be ignored + * @return {HTMLElement} The last child, ignoring ghostEl + */ + + + function lastChild(el, selector) { + var last = el.lastElementChild; + + while (last && (last === Sortable.ghost || css(last, 'display') === 'none' || selector && !matches(last, selector))) { + last = last.previousElementSibling; + } + + return last || null; + } + /** + * Returns the index of an element within its parent for a selected set of + * elements + * @param {HTMLElement} el + * @param {selector} selector + * @return {number} + */ + + + function index(el, selector) { + var index = 0; + + if (!el || !el.parentNode) { + return -1; + } + /* jshint boss:true */ + + + while (el = el.previousElementSibling) { + if (el.nodeName.toUpperCase() !== 'TEMPLATE' && el !== Sortable.clone && (!selector || matches(el, selector))) { + index++; + } + } + + return index; + } + /** + * Returns the scroll offset of the given element, added with all the scroll offsets of parent elements. + * The value is returned in real pixels. + * @param {HTMLElement} el + * @return {Array} Offsets in the format of [left, top] + */ + + + function getRelativeScrollOffset(el) { + var offsetLeft = 0, + offsetTop = 0, + winScroller = getWindowScrollingElement(); + + if (el) { + do { + var elMatrix = matrix(el), + scaleX = elMatrix.a, + scaleY = elMatrix.d; + offsetLeft += el.scrollLeft * scaleX; + offsetTop += el.scrollTop * scaleY; + } while (el !== winScroller && (el = el.parentNode)); + } + + return [offsetLeft, offsetTop]; + } + /** + * Returns the index of the object within the given array + * @param {Array} arr Array that may or may not hold the object + * @param {Object} obj An object that has a key-value pair unique to and identical to a key-value pair in the object you want to find + * @return {Number} The index of the object in the array, or -1 + */ + + + function indexOfObject(arr, obj) { + for (var i in arr) { + if (!arr.hasOwnProperty(i)) continue; + + for (var key in obj) { + if (obj.hasOwnProperty(key) && obj[key] === arr[i][key]) return Number(i); + } + } + + return -1; + } + + function getParentAutoScrollElement(el, includeSelf) { + // skip to window + if (!el || !el.getBoundingClientRect) return getWindowScrollingElement(); + var elem = el; + var gotSelf = false; + + do { + // we don't need to get elem css if it isn't even overflowing in the first place (performance) + if (elem.clientWidth < elem.scrollWidth || elem.clientHeight < elem.scrollHeight) { + var elemCSS = css(elem); + + if (elem.clientWidth < elem.scrollWidth && (elemCSS.overflowX == 'auto' || elemCSS.overflowX == 'scroll') || elem.clientHeight < elem.scrollHeight && (elemCSS.overflowY == 'auto' || elemCSS.overflowY == 'scroll')) { + if (!elem.getBoundingClientRect || elem === document.body) return getWindowScrollingElement(); + if (gotSelf || includeSelf) return elem; + gotSelf = true; + } + } + /* jshint boss:true */ + + } while (elem = elem.parentNode); + + return getWindowScrollingElement(); + } + + function extend(dst, src) { + if (dst && src) { + for (var key in src) { + if (src.hasOwnProperty(key)) { + dst[key] = src[key]; + } + } + } + + return dst; + } + + function isRectEqual(rect1, rect2) { + return Math.round(rect1.top) === Math.round(rect2.top) && Math.round(rect1.left) === Math.round(rect2.left) && Math.round(rect1.height) === Math.round(rect2.height) && Math.round(rect1.width) === Math.round(rect2.width); + } + + var _throttleTimeout; + + function throttle(callback, ms) { + return function () { + if (!_throttleTimeout) { + var args = arguments, + _this = this; + + if (args.length === 1) { + callback.call(_this, args[0]); + } else { + callback.apply(_this, args); + } + + _throttleTimeout = setTimeout(function () { + _throttleTimeout = void 0; + }, ms); + } + }; + } + + function cancelThrottle() { + clearTimeout(_throttleTimeout); + _throttleTimeout = void 0; + } + + function scrollBy(el, x, y) { + el.scrollLeft += x; + el.scrollTop += y; + } + + function clone(el) { + var Polymer = window.Polymer; + var $ = window.jQuery || window.Zepto; + + if (Polymer && Polymer.dom) { + return Polymer.dom(el).cloneNode(true); + } else if ($) { + return $(el).clone(true)[0]; + } else { + return el.cloneNode(true); + } + } + + function setRect(el, rect) { + css(el, 'position', 'absolute'); + css(el, 'top', rect.top); + css(el, 'left', rect.left); + css(el, 'width', rect.width); + css(el, 'height', rect.height); + } + + function unsetRect(el) { + css(el, 'position', ''); + css(el, 'top', ''); + css(el, 'left', ''); + css(el, 'width', ''); + css(el, 'height', ''); + } + + var expando = 'Sortable' + new Date().getTime(); + + function AnimationStateManager() { + var animationStates = [], + animationCallbackId; + return { + captureAnimationState: function captureAnimationState() { + animationStates = []; + if (!this.options.animation) return; + var children = [].slice.call(this.el.children); + children.forEach(function (child) { + if (css(child, 'display') === 'none' || child === Sortable.ghost) return; + animationStates.push({ + target: child, + rect: getRect(child) + }); + + var fromRect = _objectSpread({}, animationStates[animationStates.length - 1].rect); // If animating: compensate for current animation + + + if (child.thisAnimationDuration) { + var childMatrix = matrix(child, true); + + if (childMatrix) { + fromRect.top -= childMatrix.f; + fromRect.left -= childMatrix.e; + } + } + + child.fromRect = fromRect; + }); + }, + addAnimationState: function addAnimationState(state) { + animationStates.push(state); + }, + removeAnimationState: function removeAnimationState(target) { + animationStates.splice(indexOfObject(animationStates, { + target: target + }), 1); + }, + animateAll: function animateAll(callback) { + var _this = this; + + if (!this.options.animation) { + clearTimeout(animationCallbackId); + if (typeof callback === 'function') callback(); + return; + } + + var animating = false, + animationTime = 0; + animationStates.forEach(function (state) { + var time = 0, + target = state.target, + fromRect = target.fromRect, + toRect = getRect(target), + prevFromRect = target.prevFromRect, + prevToRect = target.prevToRect, + animatingRect = state.rect, + targetMatrix = matrix(target, true); + + if (targetMatrix) { + // Compensate for current animation + toRect.top -= targetMatrix.f; + toRect.left -= targetMatrix.e; + } + + target.toRect = toRect; + + if (target.thisAnimationDuration) { + // Could also check if animatingRect is between fromRect and toRect + if (isRectEqual(prevFromRect, toRect) && !isRectEqual(fromRect, toRect) && // Make sure animatingRect is on line between toRect & fromRect + (animatingRect.top - toRect.top) / (animatingRect.left - toRect.left) === (fromRect.top - toRect.top) / (fromRect.left - toRect.left)) { + // If returning to same place as started from animation and on same axis + time = calculateRealTime(animatingRect, prevFromRect, prevToRect, _this.options); + } + } // if fromRect != toRect: animate + + + if (!isRectEqual(toRect, fromRect)) { + target.prevFromRect = fromRect; + target.prevToRect = toRect; + + if (!time) { + time = _this.options.animation; + } + + _this.animate(target, animatingRect, toRect, time); + } + + if (time) { + animating = true; + animationTime = Math.max(animationTime, time); + clearTimeout(target.animationResetTimer); + target.animationResetTimer = setTimeout(function () { + target.animationTime = 0; + target.prevFromRect = null; + target.fromRect = null; + target.prevToRect = null; + target.thisAnimationDuration = null; + }, time); + target.thisAnimationDuration = time; + } + }); + clearTimeout(animationCallbackId); + + if (!animating) { + if (typeof callback === 'function') callback(); + } else { + animationCallbackId = setTimeout(function () { + if (typeof callback === 'function') callback(); + }, animationTime); + } + + animationStates = []; + }, + animate: function animate(target, currentRect, toRect, duration) { + if (duration) { + css(target, 'transition', ''); + css(target, 'transform', ''); + var elMatrix = matrix(this.el), + scaleX = elMatrix && elMatrix.a, + scaleY = elMatrix && elMatrix.d, + translateX = (currentRect.left - toRect.left) / (scaleX || 1), + translateY = (currentRect.top - toRect.top) / (scaleY || 1); + target.animatingX = !!translateX; + target.animatingY = !!translateY; + css(target, 'transform', 'translate3d(' + translateX + 'px,' + translateY + 'px,0)'); + repaint(target); // repaint + + css(target, 'transition', 'transform ' + duration + 'ms' + (this.options.easing ? ' ' + this.options.easing : '')); + css(target, 'transform', 'translate3d(0,0,0)'); + typeof target.animated === 'number' && clearTimeout(target.animated); + target.animated = setTimeout(function () { + css(target, 'transition', ''); + css(target, 'transform', ''); + target.animated = false; + target.animatingX = false; + target.animatingY = false; + }, duration); + } + } + }; + } + + function repaint(target) { + return target.offsetWidth; + } + + function calculateRealTime(animatingRect, fromRect, toRect, options) { + return Math.sqrt(Math.pow(fromRect.top - animatingRect.top, 2) + Math.pow(fromRect.left - animatingRect.left, 2)) / Math.sqrt(Math.pow(fromRect.top - toRect.top, 2) + Math.pow(fromRect.left - toRect.left, 2)) * options.animation; + } + + var plugins = []; + var defaults = { + initializeByDefault: true + }; + var PluginManager = { + mount: function mount(plugin) { + // Set default static properties + for (var option in defaults) { + if (defaults.hasOwnProperty(option) && !(option in plugin)) { + plugin[option] = defaults[option]; + } + } + + plugins.push(plugin); + }, + pluginEvent: function pluginEvent(eventName, sortable, evt) { + var _this = this; + + this.eventCanceled = false; + + evt.cancel = function () { + _this.eventCanceled = true; + }; + + var eventNameGlobal = eventName + 'Global'; + plugins.forEach(function (plugin) { + if (!sortable[plugin.pluginName]) return; // Fire global events if it exists in this sortable + + if (sortable[plugin.pluginName][eventNameGlobal]) { + sortable[plugin.pluginName][eventNameGlobal](_objectSpread({ + sortable: sortable + }, evt)); + } // Only fire plugin event if plugin is enabled in this sortable, + // and plugin has event defined + + + if (sortable.options[plugin.pluginName] && sortable[plugin.pluginName][eventName]) { + sortable[plugin.pluginName][eventName](_objectSpread({ + sortable: sortable + }, evt)); + } + }); + }, + initializePlugins: function initializePlugins(sortable, el, defaults, options) { + plugins.forEach(function (plugin) { + var pluginName = plugin.pluginName; + if (!sortable.options[pluginName] && !plugin.initializeByDefault) return; + var initialized = new plugin(sortable, el, sortable.options); + initialized.sortable = sortable; + initialized.options = sortable.options; + sortable[pluginName] = initialized; // Add default options from plugin + + _extends(defaults, initialized.defaults); + }); + + for (var option in sortable.options) { + if (!sortable.options.hasOwnProperty(option)) continue; + var modified = this.modifyOption(sortable, option, sortable.options[option]); + + if (typeof modified !== 'undefined') { + sortable.options[option] = modified; + } + } + }, + getEventProperties: function getEventProperties(name, sortable) { + var eventProperties = {}; + plugins.forEach(function (plugin) { + if (typeof plugin.eventProperties !== 'function') return; + + _extends(eventProperties, plugin.eventProperties.call(sortable[plugin.pluginName], name)); + }); + return eventProperties; + }, + modifyOption: function modifyOption(sortable, name, value) { + var modifiedValue; + plugins.forEach(function (plugin) { + // Plugin must exist on the Sortable + if (!sortable[plugin.pluginName]) return; // If static option listener exists for this option, call in the context of the Sortable's instance of this plugin + + if (plugin.optionListeners && typeof plugin.optionListeners[name] === 'function') { + modifiedValue = plugin.optionListeners[name].call(sortable[plugin.pluginName], value); + } + }); + return modifiedValue; + } + }; + + function dispatchEvent(_ref) { + var sortable = _ref.sortable, + rootEl = _ref.rootEl, + name = _ref.name, + targetEl = _ref.targetEl, + cloneEl = _ref.cloneEl, + toEl = _ref.toEl, + fromEl = _ref.fromEl, + oldIndex = _ref.oldIndex, + newIndex = _ref.newIndex, + oldDraggableIndex = _ref.oldDraggableIndex, + newDraggableIndex = _ref.newDraggableIndex, + originalEvent = _ref.originalEvent, + putSortable = _ref.putSortable, + extraEventProperties = _ref.extraEventProperties; + sortable = sortable || rootEl && rootEl[expando]; + if (!sortable) return; + var evt, + options = sortable.options, + onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1); // Support for new CustomEvent feature + + if (window.CustomEvent && !IE11OrLess && !Edge) { + evt = new CustomEvent(name, { + bubbles: true, + cancelable: true + }); + } else { + evt = document.createEvent('Event'); + evt.initEvent(name, true, true); + } + + evt.to = toEl || rootEl; + evt.from = fromEl || rootEl; + evt.item = targetEl || rootEl; + evt.clone = cloneEl; + evt.oldIndex = oldIndex; + evt.newIndex = newIndex; + evt.oldDraggableIndex = oldDraggableIndex; + evt.newDraggableIndex = newDraggableIndex; + evt.originalEvent = originalEvent; + evt.pullMode = putSortable ? putSortable.lastPutMode : undefined; + + var allEventProperties = _objectSpread({}, extraEventProperties, PluginManager.getEventProperties(name, sortable)); + + for (var option in allEventProperties) { + evt[option] = allEventProperties[option]; + } + + if (rootEl) { + rootEl.dispatchEvent(evt); + } + + if (options[onName]) { + options[onName].call(sortable, evt); + } + } + + var pluginEvent = function pluginEvent(eventName, sortable) { + var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, + originalEvent = _ref.evt, + data = _objectWithoutProperties(_ref, ["evt"]); + + PluginManager.pluginEvent.bind(Sortable)(eventName, sortable, _objectSpread({ + dragEl: dragEl, + parentEl: parentEl, + ghostEl: ghostEl, + rootEl: rootEl, + nextEl: nextEl, + lastDownEl: lastDownEl, + cloneEl: cloneEl, + cloneHidden: cloneHidden, + dragStarted: moved, + putSortable: putSortable, + activeSortable: Sortable.active, + originalEvent: originalEvent, + oldIndex: oldIndex, + oldDraggableIndex: oldDraggableIndex, + newIndex: newIndex, + newDraggableIndex: newDraggableIndex, + hideGhostForTarget: _hideGhostForTarget, + unhideGhostForTarget: _unhideGhostForTarget, + cloneNowHidden: function cloneNowHidden() { + cloneHidden = true; + }, + cloneNowShown: function cloneNowShown() { + cloneHidden = false; + }, + dispatchSortableEvent: function dispatchSortableEvent(name) { + _dispatchEvent({ + sortable: sortable, + name: name, + originalEvent: originalEvent + }); + } + }, data)); + }; + + function _dispatchEvent(info) { + dispatchEvent(_objectSpread({ + putSortable: putSortable, + cloneEl: cloneEl, + targetEl: dragEl, + rootEl: rootEl, + oldIndex: oldIndex, + oldDraggableIndex: oldDraggableIndex, + newIndex: newIndex, + newDraggableIndex: newDraggableIndex + }, info)); + } + + var dragEl, + parentEl, + ghostEl, + rootEl, + nextEl, + lastDownEl, + cloneEl, + cloneHidden, + oldIndex, + newIndex, + oldDraggableIndex, + newDraggableIndex, + activeGroup, + putSortable, + awaitingDragStarted = false, + ignoreNextClick = false, + sortables = [], + tapEvt, + touchEvt, + lastDx, + lastDy, + tapDistanceLeft, + tapDistanceTop, + moved, + lastTarget, + lastDirection, + pastFirstInvertThresh = false, + isCircumstantialInvert = false, + targetMoveDistance, + // For positioning ghost absolutely + ghostRelativeParent, + ghostRelativeParentInitialScroll = [], + // (left, top) + _silent = false, + savedInputChecked = []; + /** @const */ + + var documentExists = typeof document !== 'undefined', + PositionGhostAbsolutely = IOS, + CSSFloatProperty = Edge || IE11OrLess ? 'cssFloat' : 'float', + // This will not pass for IE9, because IE9 DnD only works on anchors + supportDraggable = documentExists && !ChromeForAndroid && !IOS && 'draggable' in document.createElement('div'), + supportCssPointerEvents = function () { + if (!documentExists) return; // false when <= IE11 + + if (IE11OrLess) { + return false; + } + + var el = document.createElement('x'); + el.style.cssText = 'pointer-events:auto'; + return el.style.pointerEvents === 'auto'; + }(), + _detectDirection = function _detectDirection(el, options) { + var elCSS = css(el), + elWidth = parseInt(elCSS.width) - parseInt(elCSS.paddingLeft) - parseInt(elCSS.paddingRight) - parseInt(elCSS.borderLeftWidth) - parseInt(elCSS.borderRightWidth), + child1 = getChild(el, 0, options), + child2 = getChild(el, 1, options), + firstChildCSS = child1 && css(child1), + secondChildCSS = child2 && css(child2), + firstChildWidth = firstChildCSS && parseInt(firstChildCSS.marginLeft) + parseInt(firstChildCSS.marginRight) + getRect(child1).width, + secondChildWidth = secondChildCSS && parseInt(secondChildCSS.marginLeft) + parseInt(secondChildCSS.marginRight) + getRect(child2).width; + + if (elCSS.display === 'flex') { + return elCSS.flexDirection === 'column' || elCSS.flexDirection === 'column-reverse' ? 'vertical' : 'horizontal'; + } + + if (elCSS.display === 'grid') { + return elCSS.gridTemplateColumns.split(' ').length <= 1 ? 'vertical' : 'horizontal'; + } + + if (child1 && firstChildCSS["float"] && firstChildCSS["float"] !== 'none') { + var touchingSideChild2 = firstChildCSS["float"] === 'left' ? 'left' : 'right'; + return child2 && (secondChildCSS.clear === 'both' || secondChildCSS.clear === touchingSideChild2) ? 'vertical' : 'horizontal'; + } + + return child1 && (firstChildCSS.display === 'block' || firstChildCSS.display === 'flex' || firstChildCSS.display === 'table' || firstChildCSS.display === 'grid' || firstChildWidth >= elWidth && elCSS[CSSFloatProperty] === 'none' || child2 && elCSS[CSSFloatProperty] === 'none' && firstChildWidth + secondChildWidth > elWidth) ? 'vertical' : 'horizontal'; + }, + _dragElInRowColumn = function _dragElInRowColumn(dragRect, targetRect, vertical) { + var dragElS1Opp = vertical ? dragRect.left : dragRect.top, + dragElS2Opp = vertical ? dragRect.right : dragRect.bottom, + dragElOppLength = vertical ? dragRect.width : dragRect.height, + targetS1Opp = vertical ? targetRect.left : targetRect.top, + targetS2Opp = vertical ? targetRect.right : targetRect.bottom, + targetOppLength = vertical ? targetRect.width : targetRect.height; + return dragElS1Opp === targetS1Opp || dragElS2Opp === targetS2Opp || dragElS1Opp + dragElOppLength / 2 === targetS1Opp + targetOppLength / 2; + }, + + /** + * Detects first nearest empty sortable to X and Y position using emptyInsertThreshold. + * @param {Number} x X position + * @param {Number} y Y position + * @return {HTMLElement} Element of the first found nearest Sortable + */ + _detectNearestEmptySortable = function _detectNearestEmptySortable(x, y) { + var ret; + sortables.some(function (sortable) { + if (lastChild(sortable)) return; + var rect = getRect(sortable), + threshold = sortable[expando].options.emptyInsertThreshold, + insideHorizontally = x >= rect.left - threshold && x <= rect.right + threshold, + insideVertically = y >= rect.top - threshold && y <= rect.bottom + threshold; + + if (threshold && insideHorizontally && insideVertically) { + return ret = sortable; + } + }); + return ret; + }, + _prepareGroup = function _prepareGroup(options) { + function toFn(value, pull) { + return function (to, from, dragEl, evt) { + var sameGroup = to.options.group.name && from.options.group.name && to.options.group.name === from.options.group.name; + + if (value == null && (pull || sameGroup)) { + // Default pull value + // Default pull and put value if same group + return true; + } else if (value == null || value === false) { + return false; + } else if (pull && value === 'clone') { + return value; + } else if (typeof value === 'function') { + return toFn(value(to, from, dragEl, evt), pull)(to, from, dragEl, evt); + } else { + var otherGroup = (pull ? to : from).options.group.name; + return value === true || typeof value === 'string' && value === otherGroup || value.join && value.indexOf(otherGroup) > -1; + } + }; + } + + var group = {}; + var originalGroup = options.group; + + if (!originalGroup || _typeof(originalGroup) != 'object') { + originalGroup = { + name: originalGroup + }; + } + + group.name = originalGroup.name; + group.checkPull = toFn(originalGroup.pull, true); + group.checkPut = toFn(originalGroup.put); + group.revertClone = originalGroup.revertClone; + options.group = group; + }, + _hideGhostForTarget = function _hideGhostForTarget() { + if (!supportCssPointerEvents && ghostEl) { + css(ghostEl, 'display', 'none'); + } + }, + _unhideGhostForTarget = function _unhideGhostForTarget() { + if (!supportCssPointerEvents && ghostEl) { + css(ghostEl, 'display', ''); + } + }; // #1184 fix - Prevent click event on fallback if dragged but item not changed position + + + if (documentExists) { + document.addEventListener('click', function (evt) { + if (ignoreNextClick) { + evt.preventDefault(); + evt.stopPropagation && evt.stopPropagation(); + evt.stopImmediatePropagation && evt.stopImmediatePropagation(); + ignoreNextClick = false; + return false; + } + }, true); + } + + var nearestEmptyInsertDetectEvent = function nearestEmptyInsertDetectEvent(evt) { + if (dragEl) { + evt = evt.touches ? evt.touches[0] : evt; + + var nearest = _detectNearestEmptySortable(evt.clientX, evt.clientY); + + if (nearest) { + // Create imitation event + var event = {}; + + for (var i in evt) { + if (evt.hasOwnProperty(i)) { + event[i] = evt[i]; + } + } + + event.target = event.rootEl = nearest; + event.preventDefault = void 0; + event.stopPropagation = void 0; + + nearest[expando]._onDragOver(event); + } + } + }; + + var _checkOutsideTargetEl = function _checkOutsideTargetEl(evt) { + if (dragEl) { + dragEl.parentNode[expando]._isOutsideThisEl(evt.target); + } + }; + /** + * @class Sortable + * @param {HTMLElement} el + * @param {Object} [options] + */ + + + function Sortable(el, options) { + if (!(el && el.nodeType && el.nodeType === 1)) { + throw "Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(el)); + } + + this.el = el; // root element + + this.options = options = _extends({}, options); // Export instance + + el[expando] = this; + var defaults = { + group: null, + sort: true, + disabled: false, + store: null, + handle: null, + draggable: /^[uo]l$/i.test(el.nodeName) ? '>li' : '>*', + swapThreshold: 1, + // percentage; 0 <= x <= 1 + invertSwap: false, + // invert always + invertedSwapThreshold: null, + // will be set to same as swapThreshold if default + removeCloneOnHide: true, + direction: function direction() { + return _detectDirection(el, this.options); + }, + ghostClass: 'sortable-ghost', + chosenClass: 'sortable-chosen', + dragClass: 'sortable-drag', + ignore: 'a, img', + filter: null, + preventOnFilter: true, + animation: 0, + easing: null, + setData: function setData(dataTransfer, dragEl) { + dataTransfer.setData('Text', dragEl.textContent); + }, + dropBubble: false, + dragoverBubble: false, + dataIdAttr: 'data-id', + delay: 0, + delayOnTouchOnly: false, + touchStartThreshold: (Number.parseInt ? Number : window).parseInt(window.devicePixelRatio, 10) || 1, + forceFallback: false, + fallbackClass: 'sortable-fallback', + fallbackOnBody: false, + fallbackTolerance: 0, + fallbackOffset: { + x: 0, + y: 0 + }, + supportPointer: Sortable.supportPointer !== false && 'PointerEvent' in window, + emptyInsertThreshold: 5 + }; + PluginManager.initializePlugins(this, el, defaults); // Set default options + + for (var name in defaults) { + !(name in options) && (options[name] = defaults[name]); + } + + _prepareGroup(options); // Bind all private methods + + + for (var fn in this) { + if (fn.charAt(0) === '_' && typeof this[fn] === 'function') { + this[fn] = this[fn].bind(this); + } + } // Setup drag mode + + + this.nativeDraggable = options.forceFallback ? false : supportDraggable; + + if (this.nativeDraggable) { + // Touch start threshold cannot be greater than the native dragstart threshold + this.options.touchStartThreshold = 1; + } // Bind events + + + if (options.supportPointer) { + on(el, 'pointerdown', this._onTapStart); + } else { + on(el, 'mousedown', this._onTapStart); + on(el, 'touchstart', this._onTapStart); + } + + if (this.nativeDraggable) { + on(el, 'dragover', this); + on(el, 'dragenter', this); + } + + sortables.push(this.el); // Restore sorting + + options.store && options.store.get && this.sort(options.store.get(this) || []); // Add animation state manager + + _extends(this, AnimationStateManager()); + } + + Sortable.prototype = + /** @lends Sortable.prototype */ + { + constructor: Sortable, + _isOutsideThisEl: function _isOutsideThisEl(target) { + if (!this.el.contains(target) && target !== this.el) { + lastTarget = null; + } + }, + _getDirection: function _getDirection(evt, target) { + return typeof this.options.direction === 'function' ? this.options.direction.call(this, evt, target, dragEl) : this.options.direction; + }, + _onTapStart: function _onTapStart( + /** Event|TouchEvent */ + evt) { + if (!evt.cancelable) return; + + var _this = this, + el = this.el, + options = this.options, + preventOnFilter = options.preventOnFilter, + type = evt.type, + touch = evt.touches && evt.touches[0] || evt.pointerType && evt.pointerType === 'touch' && evt, + target = (touch || evt).target, + originalTarget = evt.target.shadowRoot && (evt.path && evt.path[0] || evt.composedPath && evt.composedPath()[0]) || target, + filter = options.filter; + + _saveInputCheckedState(el); // Don't trigger start event when an element is been dragged, otherwise the evt.oldindex always wrong when set option.group. + + + if (dragEl) { + return; + } + + if (/mousedown|pointerdown/.test(type) && evt.button !== 0 || options.disabled) { + return; // only left button and enabled + } // cancel dnd if original target is content editable + + + if (originalTarget.isContentEditable) { + return; + } + + target = closest(target, options.draggable, el, false); + + if (target && target.animated) { + return; + } + + if (lastDownEl === target) { + // Ignoring duplicate `down` + return; + } // Get the index of the dragged element within its parent + + + oldIndex = index(target); + oldDraggableIndex = index(target, options.draggable); // Check filter + + if (typeof filter === 'function') { + if (filter.call(this, evt, target, this)) { + _dispatchEvent({ + sortable: _this, + rootEl: originalTarget, + name: 'filter', + targetEl: target, + toEl: el, + fromEl: el + }); + + pluginEvent('filter', _this, { + evt: evt + }); + preventOnFilter && evt.cancelable && evt.preventDefault(); + return; // cancel dnd + } + } else if (filter) { + filter = filter.split(',').some(function (criteria) { + criteria = closest(originalTarget, criteria.trim(), el, false); + + if (criteria) { + _dispatchEvent({ + sortable: _this, + rootEl: criteria, + name: 'filter', + targetEl: target, + fromEl: el, + toEl: el + }); + + pluginEvent('filter', _this, { + evt: evt + }); + return true; + } + }); + + if (filter) { + preventOnFilter && evt.cancelable && evt.preventDefault(); + return; // cancel dnd + } + } + + if (options.handle && !closest(originalTarget, options.handle, el, false)) { + return; + } // Prepare `dragstart` + + + this._prepareDragStart(evt, touch, target); + }, + _prepareDragStart: function _prepareDragStart( + /** Event */ + evt, + /** Touch */ + touch, + /** HTMLElement */ + target) { + var _this = this, + el = _this.el, + options = _this.options, + ownerDocument = el.ownerDocument, + dragStartFn; + + if (target && !dragEl && target.parentNode === el) { + var dragRect = getRect(target); + rootEl = el; + dragEl = target; + parentEl = dragEl.parentNode; + nextEl = dragEl.nextSibling; + lastDownEl = target; + activeGroup = options.group; + Sortable.dragged = dragEl; + tapEvt = { + target: dragEl, + clientX: (touch || evt).clientX, + clientY: (touch || evt).clientY + }; + tapDistanceLeft = tapEvt.clientX - dragRect.left; + tapDistanceTop = tapEvt.clientY - dragRect.top; + this._lastX = (touch || evt).clientX; + this._lastY = (touch || evt).clientY; + dragEl.style['will-change'] = 'all'; + + dragStartFn = function dragStartFn() { + pluginEvent('delayEnded', _this, { + evt: evt + }); + + if (Sortable.eventCanceled) { + _this._onDrop(); + + return; + } // Delayed drag has been triggered + // we can re-enable the events: touchmove/mousemove + + + _this._disableDelayedDragEvents(); + + if (!FireFox && _this.nativeDraggable) { + dragEl.draggable = true; + } // Bind the events: dragstart/dragend + + + _this._triggerDragStart(evt, touch); // Drag start event + + + _dispatchEvent({ + sortable: _this, + name: 'choose', + originalEvent: evt + }); // Chosen item + + + toggleClass(dragEl, options.chosenClass, true); + }; // Disable "draggable" + + + options.ignore.split(',').forEach(function (criteria) { + find(dragEl, criteria.trim(), _disableDraggable); + }); + on(ownerDocument, 'dragover', nearestEmptyInsertDetectEvent); + on(ownerDocument, 'mousemove', nearestEmptyInsertDetectEvent); + on(ownerDocument, 'touchmove', nearestEmptyInsertDetectEvent); + on(ownerDocument, 'mouseup', _this._onDrop); + on(ownerDocument, 'touchend', _this._onDrop); + on(ownerDocument, 'touchcancel', _this._onDrop); // Make dragEl draggable (must be before delay for FireFox) + + if (FireFox && this.nativeDraggable) { + this.options.touchStartThreshold = 4; + dragEl.draggable = true; + } + + pluginEvent('delayStart', this, { + evt: evt + }); // Delay is impossible for native DnD in Edge or IE + + if (options.delay && (!options.delayOnTouchOnly || touch) && (!this.nativeDraggable || !(Edge || IE11OrLess))) { + if (Sortable.eventCanceled) { + this._onDrop(); + + return; + } // If the user moves the pointer or let go the click or touch + // before the delay has been reached: + // disable the delayed drag + + + on(ownerDocument, 'mouseup', _this._disableDelayedDrag); + on(ownerDocument, 'touchend', _this._disableDelayedDrag); + on(ownerDocument, 'touchcancel', _this._disableDelayedDrag); + on(ownerDocument, 'mousemove', _this._delayedDragTouchMoveHandler); + on(ownerDocument, 'touchmove', _this._delayedDragTouchMoveHandler); + options.supportPointer && on(ownerDocument, 'pointermove', _this._delayedDragTouchMoveHandler); + _this._dragStartTimer = setTimeout(dragStartFn, options.delay); + } else { + dragStartFn(); + } + } + }, + _delayedDragTouchMoveHandler: function _delayedDragTouchMoveHandler( + /** TouchEvent|PointerEvent **/ + e) { + var touch = e.touches ? e.touches[0] : e; + + if (Math.max(Math.abs(touch.clientX - this._lastX), Math.abs(touch.clientY - this._lastY)) >= Math.floor(this.options.touchStartThreshold / (this.nativeDraggable && window.devicePixelRatio || 1))) { + this._disableDelayedDrag(); + } + }, + _disableDelayedDrag: function _disableDelayedDrag() { + dragEl && _disableDraggable(dragEl); + clearTimeout(this._dragStartTimer); + + this._disableDelayedDragEvents(); + }, + _disableDelayedDragEvents: function _disableDelayedDragEvents() { + var ownerDocument = this.el.ownerDocument; + off(ownerDocument, 'mouseup', this._disableDelayedDrag); + off(ownerDocument, 'touchend', this._disableDelayedDrag); + off(ownerDocument, 'touchcancel', this._disableDelayedDrag); + off(ownerDocument, 'mousemove', this._delayedDragTouchMoveHandler); + off(ownerDocument, 'touchmove', this._delayedDragTouchMoveHandler); + off(ownerDocument, 'pointermove', this._delayedDragTouchMoveHandler); + }, + _triggerDragStart: function _triggerDragStart( + /** Event */ + evt, + /** Touch */ + touch) { + touch = touch || evt.pointerType == 'touch' && evt; + + if (!this.nativeDraggable || touch) { + if (this.options.supportPointer) { + on(document, 'pointermove', this._onTouchMove); + } else if (touch) { + on(document, 'touchmove', this._onTouchMove); + } else { + on(document, 'mousemove', this._onTouchMove); + } + } else { + on(dragEl, 'dragend', this); + on(rootEl, 'dragstart', this._onDragStart); + } + + try { + if (document.selection) { + // Timeout neccessary for IE9 + _nextTick(function () { + document.selection.empty(); + }); + } else { + window.getSelection().removeAllRanges(); + } + } catch (err) {} + }, + _dragStarted: function _dragStarted(fallback, evt) { + + awaitingDragStarted = false; + + if (rootEl && dragEl) { + pluginEvent('dragStarted', this, { + evt: evt + }); + + if (this.nativeDraggable) { + on(document, 'dragover', _checkOutsideTargetEl); + } + + var options = this.options; // Apply effect + + !fallback && toggleClass(dragEl, options.dragClass, false); + toggleClass(dragEl, options.ghostClass, true); + Sortable.active = this; + fallback && this._appendGhost(); // Drag start event + + _dispatchEvent({ + sortable: this, + name: 'start', + originalEvent: evt + }); + } else { + this._nulling(); + } + }, + _emulateDragOver: function _emulateDragOver() { + if (touchEvt) { + this._lastX = touchEvt.clientX; + this._lastY = touchEvt.clientY; + + _hideGhostForTarget(); + + var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY); + var parent = target; + + while (target && target.shadowRoot) { + target = target.shadowRoot.elementFromPoint(touchEvt.clientX, touchEvt.clientY); + if (target === parent) break; + parent = target; + } + + dragEl.parentNode[expando]._isOutsideThisEl(target); + + if (parent) { + do { + if (parent[expando]) { + var inserted = void 0; + inserted = parent[expando]._onDragOver({ + clientX: touchEvt.clientX, + clientY: touchEvt.clientY, + target: target, + rootEl: parent + }); + + if (inserted && !this.options.dragoverBubble) { + break; + } + } + + target = parent; // store last element + } + /* jshint boss:true */ + while (parent = parent.parentNode); + } + + _unhideGhostForTarget(); + } + }, + _onTouchMove: function _onTouchMove( + /**TouchEvent*/ + evt) { + if (tapEvt) { + var options = this.options, + fallbackTolerance = options.fallbackTolerance, + fallbackOffset = options.fallbackOffset, + touch = evt.touches ? evt.touches[0] : evt, + ghostMatrix = ghostEl && matrix(ghostEl, true), + scaleX = ghostEl && ghostMatrix && ghostMatrix.a, + scaleY = ghostEl && ghostMatrix && ghostMatrix.d, + relativeScrollOffset = PositionGhostAbsolutely && ghostRelativeParent && getRelativeScrollOffset(ghostRelativeParent), + dx = (touch.clientX - tapEvt.clientX + fallbackOffset.x) / (scaleX || 1) + (relativeScrollOffset ? relativeScrollOffset[0] - ghostRelativeParentInitialScroll[0] : 0) / (scaleX || 1), + dy = (touch.clientY - tapEvt.clientY + fallbackOffset.y) / (scaleY || 1) + (relativeScrollOffset ? relativeScrollOffset[1] - ghostRelativeParentInitialScroll[1] : 0) / (scaleY || 1); // only set the status to dragging, when we are actually dragging + + if (!Sortable.active && !awaitingDragStarted) { + if (fallbackTolerance && Math.max(Math.abs(touch.clientX - this._lastX), Math.abs(touch.clientY - this._lastY)) < fallbackTolerance) { + return; + } + + this._onDragStart(evt, true); + } + + if (ghostEl) { + if (ghostMatrix) { + ghostMatrix.e += dx - (lastDx || 0); + ghostMatrix.f += dy - (lastDy || 0); + } else { + ghostMatrix = { + a: 1, + b: 0, + c: 0, + d: 1, + e: dx, + f: dy + }; + } + + var cssMatrix = "matrix(".concat(ghostMatrix.a, ",").concat(ghostMatrix.b, ",").concat(ghostMatrix.c, ",").concat(ghostMatrix.d, ",").concat(ghostMatrix.e, ",").concat(ghostMatrix.f, ")"); + css(ghostEl, 'webkitTransform', cssMatrix); + css(ghostEl, 'mozTransform', cssMatrix); + css(ghostEl, 'msTransform', cssMatrix); + css(ghostEl, 'transform', cssMatrix); + lastDx = dx; + lastDy = dy; + touchEvt = touch; + } + + evt.cancelable && evt.preventDefault(); + } + }, + _appendGhost: function _appendGhost() { + // Bug if using scale(): https://stackoverflow.com/questions/2637058 + // Not being adjusted for + if (!ghostEl) { + var container = this.options.fallbackOnBody ? document.body : rootEl, + rect = getRect(dragEl, true, PositionGhostAbsolutely, true, container), + options = this.options; // Position absolutely + + if (PositionGhostAbsolutely) { + // Get relatively positioned parent + ghostRelativeParent = container; + + while (css(ghostRelativeParent, 'position') === 'static' && css(ghostRelativeParent, 'transform') === 'none' && ghostRelativeParent !== document) { + ghostRelativeParent = ghostRelativeParent.parentNode; + } + + if (ghostRelativeParent !== document.body && ghostRelativeParent !== document.documentElement) { + if (ghostRelativeParent === document) ghostRelativeParent = getWindowScrollingElement(); + rect.top += ghostRelativeParent.scrollTop; + rect.left += ghostRelativeParent.scrollLeft; + } else { + ghostRelativeParent = getWindowScrollingElement(); + } + + ghostRelativeParentInitialScroll = getRelativeScrollOffset(ghostRelativeParent); + } + + ghostEl = dragEl.cloneNode(true); + toggleClass(ghostEl, options.ghostClass, false); + toggleClass(ghostEl, options.fallbackClass, true); + toggleClass(ghostEl, options.dragClass, true); + css(ghostEl, 'transition', ''); + css(ghostEl, 'transform', ''); + css(ghostEl, 'box-sizing', 'border-box'); + css(ghostEl, 'margin', 0); + css(ghostEl, 'top', rect.top); + css(ghostEl, 'left', rect.left); + css(ghostEl, 'width', rect.width); + css(ghostEl, 'height', rect.height); + css(ghostEl, 'opacity', '0.8'); + css(ghostEl, 'position', PositionGhostAbsolutely ? 'absolute' : 'fixed'); + css(ghostEl, 'zIndex', '100000'); + css(ghostEl, 'pointerEvents', 'none'); + Sortable.ghost = ghostEl; + container.appendChild(ghostEl); // Set transform-origin + + css(ghostEl, 'transform-origin', tapDistanceLeft / parseInt(ghostEl.style.width) * 100 + '% ' + tapDistanceTop / parseInt(ghostEl.style.height) * 100 + '%'); + } + }, + _onDragStart: function _onDragStart( + /**Event*/ + evt, + /**boolean*/ + fallback) { + var _this = this; + + var dataTransfer = evt.dataTransfer; + var options = _this.options; + pluginEvent('dragStart', this, { + evt: evt + }); + + if (Sortable.eventCanceled) { + this._onDrop(); + + return; + } + + pluginEvent('setupClone', this); + + if (!Sortable.eventCanceled) { + cloneEl = clone(dragEl); + cloneEl.draggable = false; + cloneEl.style['will-change'] = ''; + + this._hideClone(); + + toggleClass(cloneEl, this.options.chosenClass, false); + Sortable.clone = cloneEl; + } // #1143: IFrame support workaround + + + _this.cloneId = _nextTick(function () { + pluginEvent('clone', _this); + if (Sortable.eventCanceled) return; + + if (!_this.options.removeCloneOnHide) { + rootEl.insertBefore(cloneEl, dragEl); + } + + _this._hideClone(); + + _dispatchEvent({ + sortable: _this, + name: 'clone' + }); + }); + !fallback && toggleClass(dragEl, options.dragClass, true); // Set proper drop events + + if (fallback) { + ignoreNextClick = true; + _this._loopId = setInterval(_this._emulateDragOver, 50); + } else { + // Undo what was set in _prepareDragStart before drag started + off(document, 'mouseup', _this._onDrop); + off(document, 'touchend', _this._onDrop); + off(document, 'touchcancel', _this._onDrop); + + if (dataTransfer) { + dataTransfer.effectAllowed = 'move'; + options.setData && options.setData.call(_this, dataTransfer, dragEl); + } + + on(document, 'drop', _this); // #1276 fix: + + css(dragEl, 'transform', 'translateZ(0)'); + } + + awaitingDragStarted = true; + _this._dragStartId = _nextTick(_this._dragStarted.bind(_this, fallback, evt)); + on(document, 'selectstart', _this); + moved = true; + + if (Safari) { + css(document.body, 'user-select', 'none'); + } + }, + // Returns true - if no further action is needed (either inserted or another condition) + _onDragOver: function _onDragOver( + /**Event*/ + evt) { + var el = this.el, + target = evt.target, + dragRect, + targetRect, + revert, + options = this.options, + group = options.group, + activeSortable = Sortable.active, + isOwner = activeGroup === group, + canSort = options.sort, + fromSortable = putSortable || activeSortable, + vertical, + _this = this, + completedFired = false; + + if (_silent) return; + + function dragOverEvent(name, extra) { + pluginEvent(name, _this, _objectSpread({ + evt: evt, + isOwner: isOwner, + axis: vertical ? 'vertical' : 'horizontal', + revert: revert, + dragRect: dragRect, + targetRect: targetRect, + canSort: canSort, + fromSortable: fromSortable, + target: target, + completed: completed, + onMove: function onMove(target, after) { + return _onMove(rootEl, el, dragEl, dragRect, target, getRect(target), evt, after); + }, + changed: changed + }, extra)); + } // Capture animation state + + + function capture() { + dragOverEvent('dragOverAnimationCapture'); + + _this.captureAnimationState(); + + if (_this !== fromSortable) { + fromSortable.captureAnimationState(); + } + } // Return invocation when dragEl is inserted (or completed) + + + function completed(insertion) { + dragOverEvent('dragOverCompleted', { + insertion: insertion + }); + + if (insertion) { + // Clones must be hidden before folding animation to capture dragRectAbsolute properly + if (isOwner) { + activeSortable._hideClone(); + } else { + activeSortable._showClone(_this); + } + + if (_this !== fromSortable) { + // Set ghost class to new sortable's ghost class + toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : activeSortable.options.ghostClass, false); + toggleClass(dragEl, options.ghostClass, true); + } + + if (putSortable !== _this && _this !== Sortable.active) { + putSortable = _this; + } else if (_this === Sortable.active && putSortable) { + putSortable = null; + } // Animation + + + if (fromSortable === _this) { + _this._ignoreWhileAnimating = target; + } + + _this.animateAll(function () { + dragOverEvent('dragOverAnimationComplete'); + _this._ignoreWhileAnimating = null; + }); + + if (_this !== fromSortable) { + fromSortable.animateAll(); + fromSortable._ignoreWhileAnimating = null; + } + } // Null lastTarget if it is not inside a previously swapped element + + + if (target === dragEl && !dragEl.animated || target === el && !target.animated) { + lastTarget = null; + } // no bubbling and not fallback + + + if (!options.dragoverBubble && !evt.rootEl && target !== document) { + dragEl.parentNode[expando]._isOutsideThisEl(evt.target); // Do not detect for empty insert if already inserted + + + !insertion && nearestEmptyInsertDetectEvent(evt); + } + + !options.dragoverBubble && evt.stopPropagation && evt.stopPropagation(); + return completedFired = true; + } // Call when dragEl has been inserted + + + function changed() { + newIndex = index(dragEl); + newDraggableIndex = index(dragEl, options.draggable); + + _dispatchEvent({ + sortable: _this, + name: 'change', + toEl: el, + newIndex: newIndex, + newDraggableIndex: newDraggableIndex, + originalEvent: evt + }); + } + + if (evt.preventDefault !== void 0) { + evt.cancelable && evt.preventDefault(); + } + + target = closest(target, options.draggable, el, true); + dragOverEvent('dragOver'); + if (Sortable.eventCanceled) return completedFired; + + if (dragEl.contains(evt.target) || target.animated && target.animatingX && target.animatingY || _this._ignoreWhileAnimating === target) { + return completed(false); + } + + ignoreNextClick = false; + + if (activeSortable && !options.disabled && (isOwner ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list + : putSortable === this || (this.lastPutMode = activeGroup.checkPull(this, activeSortable, dragEl, evt)) && group.checkPut(this, activeSortable, dragEl, evt))) { + vertical = this._getDirection(evt, target) === 'vertical'; + dragRect = getRect(dragEl); + dragOverEvent('dragOverValid'); + if (Sortable.eventCanceled) return completedFired; + + if (revert) { + parentEl = rootEl; // actualization + + capture(); + + this._hideClone(); + + dragOverEvent('revert'); + + if (!Sortable.eventCanceled) { + if (nextEl) { + rootEl.insertBefore(dragEl, nextEl); + } else { + rootEl.appendChild(dragEl); + } + } + + return completed(true); + } + + var elLastChild = lastChild(el, options.draggable); + + if (!elLastChild || _ghostIsLast(evt, vertical, this) && !elLastChild.animated) { + // If already at end of list: Do not insert + if (elLastChild === dragEl) { + return completed(false); + } // assign target only if condition is true + + + if (elLastChild && el === evt.target) { + target = elLastChild; + } + + if (target) { + targetRect = getRect(target); + } + + if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, !!target) !== false) { + capture(); + el.appendChild(dragEl); + parentEl = el; // actualization + + changed(); + return completed(true); + } + } else if (target.parentNode === el) { + targetRect = getRect(target); + var direction = 0, + targetBeforeFirstSwap, + differentLevel = dragEl.parentNode !== el, + differentRowCol = !_dragElInRowColumn(dragEl.animated && dragEl.toRect || dragRect, target.animated && target.toRect || targetRect, vertical), + side1 = vertical ? 'top' : 'left', + scrolledPastTop = isScrolledPast(target, 'top', 'top') || isScrolledPast(dragEl, 'top', 'top'), + scrollBefore = scrolledPastTop ? scrolledPastTop.scrollTop : void 0; + + if (lastTarget !== target) { + targetBeforeFirstSwap = targetRect[side1]; + pastFirstInvertThresh = false; + isCircumstantialInvert = !differentRowCol && options.invertSwap || differentLevel; + } + + direction = _getSwapDirection(evt, target, targetRect, vertical, differentRowCol ? 1 : options.swapThreshold, options.invertedSwapThreshold == null ? options.swapThreshold : options.invertedSwapThreshold, isCircumstantialInvert, lastTarget === target); + var sibling; + + if (direction !== 0) { + // Check if target is beside dragEl in respective direction (ignoring hidden elements) + var dragIndex = index(dragEl); + + do { + dragIndex -= direction; + sibling = parentEl.children[dragIndex]; + } while (sibling && (css(sibling, 'display') === 'none' || sibling === ghostEl)); + } // If dragEl is already beside target: Do not insert + + + if (direction === 0 || sibling === target) { + return completed(false); + } + + lastTarget = target; + lastDirection = direction; + var nextSibling = target.nextElementSibling, + after = false; + after = direction === 1; + + var moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, after); + + if (moveVector !== false) { + if (moveVector === 1 || moveVector === -1) { + after = moveVector === 1; + } + + _silent = true; + setTimeout(_unsilent, 30); + capture(); + + if (after && !nextSibling) { + el.appendChild(dragEl); + } else { + target.parentNode.insertBefore(dragEl, after ? nextSibling : target); + } // Undo chrome's scroll adjustment (has no effect on other browsers) + + + if (scrolledPastTop) { + scrollBy(scrolledPastTop, 0, scrollBefore - scrolledPastTop.scrollTop); + } + + parentEl = dragEl.parentNode; // actualization + // must be done before animation + + if (targetBeforeFirstSwap !== undefined && !isCircumstantialInvert) { + targetMoveDistance = Math.abs(targetBeforeFirstSwap - getRect(target)[side1]); + } + + changed(); + return completed(true); + } + } + + if (el.contains(dragEl)) { + return completed(false); + } + } + + return false; + }, + _ignoreWhileAnimating: null, + _offMoveEvents: function _offMoveEvents() { + off(document, 'mousemove', this._onTouchMove); + off(document, 'touchmove', this._onTouchMove); + off(document, 'pointermove', this._onTouchMove); + off(document, 'dragover', nearestEmptyInsertDetectEvent); + off(document, 'mousemove', nearestEmptyInsertDetectEvent); + off(document, 'touchmove', nearestEmptyInsertDetectEvent); + }, + _offUpEvents: function _offUpEvents() { + var ownerDocument = this.el.ownerDocument; + off(ownerDocument, 'mouseup', this._onDrop); + off(ownerDocument, 'touchend', this._onDrop); + off(ownerDocument, 'pointerup', this._onDrop); + off(ownerDocument, 'touchcancel', this._onDrop); + off(document, 'selectstart', this); + }, + _onDrop: function _onDrop( + /**Event*/ + evt) { + var el = this.el, + options = this.options; // Get the index of the dragged element within its parent + + newIndex = index(dragEl); + newDraggableIndex = index(dragEl, options.draggable); + pluginEvent('drop', this, { + evt: evt + }); + parentEl = dragEl && dragEl.parentNode; // Get again after plugin event + + newIndex = index(dragEl); + newDraggableIndex = index(dragEl, options.draggable); + + if (Sortable.eventCanceled) { + this._nulling(); + + return; + } + + awaitingDragStarted = false; + isCircumstantialInvert = false; + pastFirstInvertThresh = false; + clearInterval(this._loopId); + clearTimeout(this._dragStartTimer); + + _cancelNextTick(this.cloneId); + + _cancelNextTick(this._dragStartId); // Unbind events + + + if (this.nativeDraggable) { + off(document, 'drop', this); + off(el, 'dragstart', this._onDragStart); + } + + this._offMoveEvents(); + + this._offUpEvents(); + + if (Safari) { + css(document.body, 'user-select', ''); + } + + css(dragEl, 'transform', ''); + + if (evt) { + if (moved) { + evt.cancelable && evt.preventDefault(); + !options.dropBubble && evt.stopPropagation(); + } + + ghostEl && ghostEl.parentNode && ghostEl.parentNode.removeChild(ghostEl); + + if (rootEl === parentEl || putSortable && putSortable.lastPutMode !== 'clone') { + // Remove clone(s) + cloneEl && cloneEl.parentNode && cloneEl.parentNode.removeChild(cloneEl); + } + + if (dragEl) { + if (this.nativeDraggable) { + off(dragEl, 'dragend', this); + } + + _disableDraggable(dragEl); + + dragEl.style['will-change'] = ''; // Remove classes + // ghostClass is added in dragStarted + + if (moved && !awaitingDragStarted) { + toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : this.options.ghostClass, false); + } + + toggleClass(dragEl, this.options.chosenClass, false); // Drag stop event + + _dispatchEvent({ + sortable: this, + name: 'unchoose', + toEl: parentEl, + newIndex: null, + newDraggableIndex: null, + originalEvent: evt + }); + + if (rootEl !== parentEl) { + if (newIndex >= 0) { + // Add event + _dispatchEvent({ + rootEl: parentEl, + name: 'add', + toEl: parentEl, + fromEl: rootEl, + originalEvent: evt + }); // Remove event + + + _dispatchEvent({ + sortable: this, + name: 'remove', + toEl: parentEl, + originalEvent: evt + }); // drag from one list and drop into another + + + _dispatchEvent({ + rootEl: parentEl, + name: 'sort', + toEl: parentEl, + fromEl: rootEl, + originalEvent: evt + }); + + _dispatchEvent({ + sortable: this, + name: 'sort', + toEl: parentEl, + originalEvent: evt + }); + } + + putSortable && putSortable.save(); + } else { + if (newIndex !== oldIndex) { + if (newIndex >= 0) { + // drag & drop within the same list + _dispatchEvent({ + sortable: this, + name: 'update', + toEl: parentEl, + originalEvent: evt + }); + + _dispatchEvent({ + sortable: this, + name: 'sort', + toEl: parentEl, + originalEvent: evt + }); + } + } + } + + if (Sortable.active) { + /* jshint eqnull:true */ + if (newIndex == null || newIndex === -1) { + newIndex = oldIndex; + newDraggableIndex = oldDraggableIndex; + } + + _dispatchEvent({ + sortable: this, + name: 'end', + toEl: parentEl, + originalEvent: evt + }); // Save sorting + + + this.save(); + } + } + } + + this._nulling(); + }, + _nulling: function _nulling() { + pluginEvent('nulling', this); + rootEl = dragEl = parentEl = ghostEl = nextEl = cloneEl = lastDownEl = cloneHidden = tapEvt = touchEvt = moved = newIndex = newDraggableIndex = oldIndex = oldDraggableIndex = lastTarget = lastDirection = putSortable = activeGroup = Sortable.dragged = Sortable.ghost = Sortable.clone = Sortable.active = null; + savedInputChecked.forEach(function (el) { + el.checked = true; + }); + savedInputChecked.length = lastDx = lastDy = 0; + }, + handleEvent: function handleEvent( + /**Event*/ + evt) { + switch (evt.type) { + case 'drop': + case 'dragend': + this._onDrop(evt); + + break; + + case 'dragenter': + case 'dragover': + if (dragEl) { + this._onDragOver(evt); + + _globalDragOver(evt); + } + + break; + + case 'selectstart': + evt.preventDefault(); + break; + } + }, + + /** + * Serializes the item into an array of string. + * @returns {String[]} + */ + toArray: function toArray() { + var order = [], + el, + children = this.el.children, + i = 0, + n = children.length, + options = this.options; + + for (; i < n; i++) { + el = children[i]; + + if (closest(el, options.draggable, this.el, false)) { + order.push(el.getAttribute(options.dataIdAttr) || _generateId(el)); + } + } + + return order; + }, + + /** + * Sorts the elements according to the array. + * @param {String[]} order order of the items + */ + sort: function sort(order) { + var items = {}, + rootEl = this.el; + this.toArray().forEach(function (id, i) { + var el = rootEl.children[i]; + + if (closest(el, this.options.draggable, rootEl, false)) { + items[id] = el; + } + }, this); + order.forEach(function (id) { + if (items[id]) { + rootEl.removeChild(items[id]); + rootEl.appendChild(items[id]); + } + }); + }, + + /** + * Save the current sorting + */ + save: function save() { + var store = this.options.store; + store && store.set && store.set(this); + }, + + /** + * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. + * @param {HTMLElement} el + * @param {String} [selector] default: `options.draggable` + * @returns {HTMLElement|null} + */ + closest: function closest$1(el, selector) { + return closest(el, selector || this.options.draggable, this.el, false); + }, + + /** + * Set/get option + * @param {string} name + * @param {*} [value] + * @returns {*} + */ + option: function option(name, value) { + var options = this.options; + + if (value === void 0) { + return options[name]; + } else { + var modifiedValue = PluginManager.modifyOption(this, name, value); + + if (typeof modifiedValue !== 'undefined') { + options[name] = modifiedValue; + } else { + options[name] = value; + } + + if (name === 'group') { + _prepareGroup(options); + } + } + }, + + /** + * Destroy + */ + destroy: function destroy() { + pluginEvent('destroy', this); + var el = this.el; + el[expando] = null; + off(el, 'mousedown', this._onTapStart); + off(el, 'touchstart', this._onTapStart); + off(el, 'pointerdown', this._onTapStart); + + if (this.nativeDraggable) { + off(el, 'dragover', this); + off(el, 'dragenter', this); + } // Remove draggable attributes + + + Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) { + el.removeAttribute('draggable'); + }); + + this._onDrop(); + + this._disableDelayedDragEvents(); + + sortables.splice(sortables.indexOf(this.el), 1); + this.el = el = null; + }, + _hideClone: function _hideClone() { + if (!cloneHidden) { + pluginEvent('hideClone', this); + if (Sortable.eventCanceled) return; + css(cloneEl, 'display', 'none'); + + if (this.options.removeCloneOnHide && cloneEl.parentNode) { + cloneEl.parentNode.removeChild(cloneEl); + } + + cloneHidden = true; + } + }, + _showClone: function _showClone(putSortable) { + if (putSortable.lastPutMode !== 'clone') { + this._hideClone(); + + return; + } + + if (cloneHidden) { + pluginEvent('showClone', this); + if (Sortable.eventCanceled) return; // show clone at dragEl or original position + + if (rootEl.contains(dragEl) && !this.options.group.revertClone) { + rootEl.insertBefore(cloneEl, dragEl); + } else if (nextEl) { + rootEl.insertBefore(cloneEl, nextEl); + } else { + rootEl.appendChild(cloneEl); + } + + if (this.options.group.revertClone) { + this.animate(dragEl, cloneEl); + } + + css(cloneEl, 'display', ''); + cloneHidden = false; + } + } + }; + + function _globalDragOver( + /**Event*/ + evt) { + if (evt.dataTransfer) { + evt.dataTransfer.dropEffect = 'move'; + } + + evt.cancelable && evt.preventDefault(); + } + + function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvent, willInsertAfter) { + var evt, + sortable = fromEl[expando], + onMoveFn = sortable.options.onMove, + retVal; // Support for new CustomEvent feature + + if (window.CustomEvent && !IE11OrLess && !Edge) { + evt = new CustomEvent('move', { + bubbles: true, + cancelable: true + }); + } else { + evt = document.createEvent('Event'); + evt.initEvent('move', true, true); + } + + evt.to = toEl; + evt.from = fromEl; + evt.dragged = dragEl; + evt.draggedRect = dragRect; + evt.related = targetEl || toEl; + evt.relatedRect = targetRect || getRect(toEl); + evt.willInsertAfter = willInsertAfter; + evt.originalEvent = originalEvent; + fromEl.dispatchEvent(evt); + + if (onMoveFn) { + retVal = onMoveFn.call(sortable, evt, originalEvent); + } + + return retVal; + } + + function _disableDraggable(el) { + el.draggable = false; + } + + function _unsilent() { + _silent = false; + } + + function _ghostIsLast(evt, vertical, sortable) { + var rect = getRect(lastChild(sortable.el, sortable.options.draggable)); + var spacer = 10; + return vertical ? evt.clientX > rect.right + spacer || evt.clientX <= rect.right && evt.clientY > rect.bottom && evt.clientX >= rect.left : evt.clientX > rect.right && evt.clientY > rect.top || evt.clientX <= rect.right && evt.clientY > rect.bottom + spacer; + } + + function _getSwapDirection(evt, target, targetRect, vertical, swapThreshold, invertedSwapThreshold, invertSwap, isLastTarget) { + var mouseOnAxis = vertical ? evt.clientY : evt.clientX, + targetLength = vertical ? targetRect.height : targetRect.width, + targetS1 = vertical ? targetRect.top : targetRect.left, + targetS2 = vertical ? targetRect.bottom : targetRect.right, + invert = false; + + if (!invertSwap) { + // Never invert or create dragEl shadow when target movemenet causes mouse to move past the end of regular swapThreshold + if (isLastTarget && targetMoveDistance < targetLength * swapThreshold) { + // multiplied only by swapThreshold because mouse will already be inside target by (1 - threshold) * targetLength / 2 + // check if past first invert threshold on side opposite of lastDirection + if (!pastFirstInvertThresh && (lastDirection === 1 ? mouseOnAxis > targetS1 + targetLength * invertedSwapThreshold / 2 : mouseOnAxis < targetS2 - targetLength * invertedSwapThreshold / 2)) { + // past first invert threshold, do not restrict inverted threshold to dragEl shadow + pastFirstInvertThresh = true; + } + + if (!pastFirstInvertThresh) { + // dragEl shadow (target move distance shadow) + if (lastDirection === 1 ? mouseOnAxis < targetS1 + targetMoveDistance // over dragEl shadow + : mouseOnAxis > targetS2 - targetMoveDistance) { + return -lastDirection; + } + } else { + invert = true; + } + } else { + // Regular + if (mouseOnAxis > targetS1 + targetLength * (1 - swapThreshold) / 2 && mouseOnAxis < targetS2 - targetLength * (1 - swapThreshold) / 2) { + return _getInsertDirection(target); + } + } + } + + invert = invert || invertSwap; + + if (invert) { + // Invert of regular + if (mouseOnAxis < targetS1 + targetLength * invertedSwapThreshold / 2 || mouseOnAxis > targetS2 - targetLength * invertedSwapThreshold / 2) { + return mouseOnAxis > targetS1 + targetLength / 2 ? 1 : -1; + } + } + + return 0; + } + /** + * Gets the direction dragEl must be swapped relative to target in order to make it + * seem that dragEl has been "inserted" into that element's position + * @param {HTMLElement} target The target whose position dragEl is being inserted at + * @return {Number} Direction dragEl must be swapped + */ + + + function _getInsertDirection(target) { + if (index(dragEl) < index(target)) { + return 1; + } else { + return -1; + } + } + /** + * Generate id + * @param {HTMLElement} el + * @returns {String} + * @private + */ + + + function _generateId(el) { + var str = el.tagName + el.className + el.src + el.href + el.textContent, + i = str.length, + sum = 0; + + while (i--) { + sum += str.charCodeAt(i); + } + + return sum.toString(36); + } + + function _saveInputCheckedState(root) { + savedInputChecked.length = 0; + var inputs = root.getElementsByTagName('input'); + var idx = inputs.length; + + while (idx--) { + var el = inputs[idx]; + el.checked && savedInputChecked.push(el); + } + } + + function _nextTick(fn) { + return setTimeout(fn, 0); + } + + function _cancelNextTick(id) { + return clearTimeout(id); + } // Fixed #973: + + + if (documentExists) { + on(document, 'touchmove', function (evt) { + if ((Sortable.active || awaitingDragStarted) && evt.cancelable) { + evt.preventDefault(); + } + }); + } // Export utils + + + Sortable.utils = { + on: on, + off: off, + css: css, + find: find, + is: function is(el, selector) { + return !!closest(el, selector, el, false); + }, + extend: extend, + throttle: throttle, + closest: closest, + toggleClass: toggleClass, + clone: clone, + index: index, + nextTick: _nextTick, + cancelNextTick: _cancelNextTick, + detectDirection: _detectDirection, + getChild: getChild + }; + /** + * Get the Sortable instance of an element + * @param {HTMLElement} element The element + * @return {Sortable|undefined} The instance of Sortable + */ + + Sortable.get = function (element) { + return element[expando]; + }; + /** + * Mount a plugin to Sortable + * @param {...SortablePlugin|SortablePlugin[]} plugins Plugins being mounted + */ + + + Sortable.mount = function () { + for (var _len = arguments.length, plugins = new Array(_len), _key = 0; _key < _len; _key++) { + plugins[_key] = arguments[_key]; + } + + if (plugins[0].constructor === Array) plugins = plugins[0]; + plugins.forEach(function (plugin) { + if (!plugin.prototype || !plugin.prototype.constructor) { + throw "Sortable: Mounted plugin must be a constructor function, not ".concat({}.toString.call(plugin)); + } + + if (plugin.utils) Sortable.utils = _objectSpread({}, Sortable.utils, plugin.utils); + PluginManager.mount(plugin); + }); + }; + /** + * Create sortable instance + * @param {HTMLElement} el + * @param {Object} [options] + */ + + + Sortable.create = function (el, options) { + return new Sortable(el, options); + }; // Export + + + Sortable.version = version; + + var autoScrolls = [], + scrollEl, + scrollRootEl, + scrolling = false, + lastAutoScrollX, + lastAutoScrollY, + touchEvt$1, + pointerElemChangedInterval; + + function AutoScrollPlugin() { + function AutoScroll() { + this.defaults = { + scroll: true, + scrollSensitivity: 30, + scrollSpeed: 10, + bubbleScroll: true + }; // Bind all private methods + + for (var fn in this) { + if (fn.charAt(0) === '_' && typeof this[fn] === 'function') { + this[fn] = this[fn].bind(this); + } + } + } + + AutoScroll.prototype = { + dragStarted: function dragStarted(_ref) { + var originalEvent = _ref.originalEvent; + + if (this.sortable.nativeDraggable) { + on(document, 'dragover', this._handleAutoScroll); + } else { + if (this.options.supportPointer) { + on(document, 'pointermove', this._handleFallbackAutoScroll); + } else if (originalEvent.touches) { + on(document, 'touchmove', this._handleFallbackAutoScroll); + } else { + on(document, 'mousemove', this._handleFallbackAutoScroll); + } + } + }, + dragOverCompleted: function dragOverCompleted(_ref2) { + var originalEvent = _ref2.originalEvent; + + // For when bubbling is canceled and using fallback (fallback 'touchmove' always reached) + if (!this.options.dragOverBubble && !originalEvent.rootEl) { + this._handleAutoScroll(originalEvent); + } + }, + drop: function drop() { + if (this.sortable.nativeDraggable) { + off(document, 'dragover', this._handleAutoScroll); + } else { + off(document, 'pointermove', this._handleFallbackAutoScroll); + off(document, 'touchmove', this._handleFallbackAutoScroll); + off(document, 'mousemove', this._handleFallbackAutoScroll); + } + + clearPointerElemChangedInterval(); + clearAutoScrolls(); + cancelThrottle(); + }, + nulling: function nulling() { + touchEvt$1 = scrollRootEl = scrollEl = scrolling = pointerElemChangedInterval = lastAutoScrollX = lastAutoScrollY = null; + autoScrolls.length = 0; + }, + _handleFallbackAutoScroll: function _handleFallbackAutoScroll(evt) { + this._handleAutoScroll(evt, true); + }, + _handleAutoScroll: function _handleAutoScroll(evt, fallback) { + var _this = this; + + var x = (evt.touches ? evt.touches[0] : evt).clientX, + y = (evt.touches ? evt.touches[0] : evt).clientY, + elem = document.elementFromPoint(x, y); + touchEvt$1 = evt; // IE does not seem to have native autoscroll, + // Edge's autoscroll seems too conditional, + // MACOS Safari does not have autoscroll, + // Firefox and Chrome are good + + if (fallback || Edge || IE11OrLess || Safari) { + autoScroll(evt, this.options, elem, fallback); // Listener for pointer element change + + var ogElemScroller = getParentAutoScrollElement(elem, true); + + if (scrolling && (!pointerElemChangedInterval || x !== lastAutoScrollX || y !== lastAutoScrollY)) { + pointerElemChangedInterval && clearPointerElemChangedInterval(); // Detect for pointer elem change, emulating native DnD behaviour + + pointerElemChangedInterval = setInterval(function () { + var newElem = getParentAutoScrollElement(document.elementFromPoint(x, y), true); + + if (newElem !== ogElemScroller) { + ogElemScroller = newElem; + clearAutoScrolls(); + } + + autoScroll(evt, _this.options, newElem, fallback); + }, 10); + lastAutoScrollX = x; + lastAutoScrollY = y; + } + } else { + // if DnD is enabled (and browser has good autoscrolling), first autoscroll will already scroll, so get parent autoscroll of first autoscroll + if (!this.options.bubbleScroll || getParentAutoScrollElement(elem, true) === getWindowScrollingElement()) { + clearAutoScrolls(); + return; + } + + autoScroll(evt, this.options, getParentAutoScrollElement(elem, false), false); + } + } + }; + return _extends(AutoScroll, { + pluginName: 'scroll', + initializeByDefault: true + }); + } + + function clearAutoScrolls() { + autoScrolls.forEach(function (autoScroll) { + clearInterval(autoScroll.pid); + }); + autoScrolls = []; + } + + function clearPointerElemChangedInterval() { + clearInterval(pointerElemChangedInterval); + } + + var autoScroll = throttle(function (evt, options, rootEl, isFallback) { + // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521 + if (!options.scroll) return; + var x = (evt.touches ? evt.touches[0] : evt).clientX, + y = (evt.touches ? evt.touches[0] : evt).clientY, + sens = options.scrollSensitivity, + speed = options.scrollSpeed, + winScroller = getWindowScrollingElement(); + var scrollThisInstance = false, + scrollCustomFn; // New scroll root, set scrollEl + + if (scrollRootEl !== rootEl) { + scrollRootEl = rootEl; + clearAutoScrolls(); + scrollEl = options.scroll; + scrollCustomFn = options.scrollFn; + + if (scrollEl === true) { + scrollEl = getParentAutoScrollElement(rootEl, true); + } + } + + var layersOut = 0; + var currentParent = scrollEl; + + do { + var el = currentParent, + rect = getRect(el), + top = rect.top, + bottom = rect.bottom, + left = rect.left, + right = rect.right, + width = rect.width, + height = rect.height, + canScrollX = void 0, + canScrollY = void 0, + scrollWidth = el.scrollWidth, + scrollHeight = el.scrollHeight, + elCSS = css(el), + scrollPosX = el.scrollLeft, + scrollPosY = el.scrollTop; + + if (el === winScroller) { + canScrollX = width < scrollWidth && (elCSS.overflowX === 'auto' || elCSS.overflowX === 'scroll' || elCSS.overflowX === 'visible'); + canScrollY = height < scrollHeight && (elCSS.overflowY === 'auto' || elCSS.overflowY === 'scroll' || elCSS.overflowY === 'visible'); + } else { + canScrollX = width < scrollWidth && (elCSS.overflowX === 'auto' || elCSS.overflowX === 'scroll'); + canScrollY = height < scrollHeight && (elCSS.overflowY === 'auto' || elCSS.overflowY === 'scroll'); + } + + var vx = canScrollX && (Math.abs(right - x) <= sens && scrollPosX + width < scrollWidth) - (Math.abs(left - x) <= sens && !!scrollPosX); + var vy = canScrollY && (Math.abs(bottom - y) <= sens && scrollPosY + height < scrollHeight) - (Math.abs(top - y) <= sens && !!scrollPosY); + + if (!autoScrolls[layersOut]) { + for (var i = 0; i <= layersOut; i++) { + if (!autoScrolls[i]) { + autoScrolls[i] = {}; + } + } + } + + if (autoScrolls[layersOut].vx != vx || autoScrolls[layersOut].vy != vy || autoScrolls[layersOut].el !== el) { + autoScrolls[layersOut].el = el; + autoScrolls[layersOut].vx = vx; + autoScrolls[layersOut].vy = vy; + clearInterval(autoScrolls[layersOut].pid); + + if (vx != 0 || vy != 0) { + scrollThisInstance = true; + /* jshint loopfunc:true */ + + autoScrolls[layersOut].pid = setInterval(function () { + // emulate drag over during autoscroll (fallback), emulating native DnD behaviour + if (isFallback && this.layer === 0) { + Sortable.active._onTouchMove(touchEvt$1); // To move ghost if it is positioned absolutely + + } + + var scrollOffsetY = autoScrolls[this.layer].vy ? autoScrolls[this.layer].vy * speed : 0; + var scrollOffsetX = autoScrolls[this.layer].vx ? autoScrolls[this.layer].vx * speed : 0; + + if (typeof scrollCustomFn === 'function') { + if (scrollCustomFn.call(Sortable.dragged.parentNode[expando], scrollOffsetX, scrollOffsetY, evt, touchEvt$1, autoScrolls[this.layer].el) !== 'continue') { + return; + } + } + + scrollBy(autoScrolls[this.layer].el, scrollOffsetX, scrollOffsetY); + }.bind({ + layer: layersOut + }), 24); + } + } + + layersOut++; + } while (options.bubbleScroll && currentParent !== winScroller && (currentParent = getParentAutoScrollElement(currentParent, false))); + + scrolling = scrollThisInstance; // in case another function catches scrolling as false in between when it is not + }, 30); + + var drop = function drop(_ref) { + var originalEvent = _ref.originalEvent, + putSortable = _ref.putSortable, + dragEl = _ref.dragEl, + activeSortable = _ref.activeSortable, + dispatchSortableEvent = _ref.dispatchSortableEvent, + hideGhostForTarget = _ref.hideGhostForTarget, + unhideGhostForTarget = _ref.unhideGhostForTarget; + if (!originalEvent) return; + var toSortable = putSortable || activeSortable; + hideGhostForTarget(); + var touch = originalEvent.changedTouches && originalEvent.changedTouches.length ? originalEvent.changedTouches[0] : originalEvent; + var target = document.elementFromPoint(touch.clientX, touch.clientY); + unhideGhostForTarget(); + + if (toSortable && !toSortable.el.contains(target)) { + dispatchSortableEvent('spill'); + this.onSpill({ + dragEl: dragEl, + putSortable: putSortable + }); + } + }; + + function Revert() {} + + Revert.prototype = { + startIndex: null, + dragStart: function dragStart(_ref2) { + var oldDraggableIndex = _ref2.oldDraggableIndex; + this.startIndex = oldDraggableIndex; + }, + onSpill: function onSpill(_ref3) { + var dragEl = _ref3.dragEl, + putSortable = _ref3.putSortable; + this.sortable.captureAnimationState(); + + if (putSortable) { + putSortable.captureAnimationState(); + } + + var nextSibling = getChild(this.sortable.el, this.startIndex, this.options); + + if (nextSibling) { + this.sortable.el.insertBefore(dragEl, nextSibling); + } else { + this.sortable.el.appendChild(dragEl); + } + + this.sortable.animateAll(); + + if (putSortable) { + putSortable.animateAll(); + } + }, + drop: drop + }; + + _extends(Revert, { + pluginName: 'revertOnSpill' + }); + + function Remove() {} + + Remove.prototype = { + onSpill: function onSpill(_ref4) { + var dragEl = _ref4.dragEl, + putSortable = _ref4.putSortable; + var parentSortable = putSortable || this.sortable; + parentSortable.captureAnimationState(); + dragEl.parentNode && dragEl.parentNode.removeChild(dragEl); + parentSortable.animateAll(); + }, + drop: drop + }; + + _extends(Remove, { + pluginName: 'removeOnSpill' + }); + + var lastSwapEl; + + function SwapPlugin() { + function Swap() { + this.defaults = { + swapClass: 'sortable-swap-highlight' + }; + } + + Swap.prototype = { + dragStart: function dragStart(_ref) { + var dragEl = _ref.dragEl; + lastSwapEl = dragEl; + }, + dragOverValid: function dragOverValid(_ref2) { + var completed = _ref2.completed, + target = _ref2.target, + onMove = _ref2.onMove, + activeSortable = _ref2.activeSortable, + changed = _ref2.changed, + cancel = _ref2.cancel; + if (!activeSortable.options.swap) return; + var el = this.sortable.el, + options = this.options; + + if (target && target !== el) { + var prevSwapEl = lastSwapEl; + + if (onMove(target) !== false) { + toggleClass(target, options.swapClass, true); + lastSwapEl = target; + } else { + lastSwapEl = null; + } + + if (prevSwapEl && prevSwapEl !== lastSwapEl) { + toggleClass(prevSwapEl, options.swapClass, false); + } + } + + changed(); + completed(true); + cancel(); + }, + drop: function drop(_ref3) { + var activeSortable = _ref3.activeSortable, + putSortable = _ref3.putSortable, + dragEl = _ref3.dragEl; + var toSortable = putSortable || this.sortable; + var options = this.options; + lastSwapEl && toggleClass(lastSwapEl, options.swapClass, false); + + if (lastSwapEl && (options.swap || putSortable && putSortable.options.swap)) { + if (dragEl !== lastSwapEl) { + toSortable.captureAnimationState(); + if (toSortable !== activeSortable) activeSortable.captureAnimationState(); + swapNodes(dragEl, lastSwapEl); + toSortable.animateAll(); + if (toSortable !== activeSortable) activeSortable.animateAll(); + } + } + }, + nulling: function nulling() { + lastSwapEl = null; + } + }; + return _extends(Swap, { + pluginName: 'swap', + eventProperties: function eventProperties() { + return { + swapItem: lastSwapEl + }; + } + }); + } + + function swapNodes(n1, n2) { + var p1 = n1.parentNode, + p2 = n2.parentNode, + i1, + i2; + if (!p1 || !p2 || p1.isEqualNode(n2) || p2.isEqualNode(n1)) return; + i1 = index(n1); + i2 = index(n2); + + if (p1.isEqualNode(p2) && i1 < i2) { + i2++; + } + + p1.insertBefore(n2, p1.children[i1]); + p2.insertBefore(n1, p2.children[i2]); + } + + var multiDragElements = [], + multiDragClones = [], + lastMultiDragSelect, + // for selection with modifier key down (SHIFT) + multiDragSortable, + initialFolding = false, + // Initial multi-drag fold when drag started + folding = false, + // Folding any other time + dragStarted = false, + dragEl$1, + clonesFromRect, + clonesHidden; + + function MultiDragPlugin() { + function MultiDrag(sortable) { + // Bind all private methods + for (var fn in this) { + if (fn.charAt(0) === '_' && typeof this[fn] === 'function') { + this[fn] = this[fn].bind(this); + } + } + + if (sortable.options.supportPointer) { + on(document, 'pointerup', this._deselectMultiDrag); + } else { + on(document, 'mouseup', this._deselectMultiDrag); + on(document, 'touchend', this._deselectMultiDrag); + } + + on(document, 'keydown', this._checkKeyDown); + on(document, 'keyup', this._checkKeyUp); + this.defaults = { + selectedClass: 'sortable-selected', + multiDragKey: null, + setData: function setData(dataTransfer, dragEl) { + var data = ''; + + if (multiDragElements.length && multiDragSortable === sortable) { + multiDragElements.forEach(function (multiDragElement, i) { + data += (!i ? '' : ', ') + multiDragElement.textContent; + }); + } else { + data = dragEl.textContent; + } + + dataTransfer.setData('Text', data); + } + }; + } + + MultiDrag.prototype = { + multiDragKeyDown: false, + isMultiDrag: false, + delayStartGlobal: function delayStartGlobal(_ref) { + var dragged = _ref.dragEl; + dragEl$1 = dragged; + }, + delayEnded: function delayEnded() { + this.isMultiDrag = ~multiDragElements.indexOf(dragEl$1); + }, + setupClone: function setupClone(_ref2) { + var sortable = _ref2.sortable, + cancel = _ref2.cancel; + if (!this.isMultiDrag) return; + + for (var i = 0; i < multiDragElements.length; i++) { + multiDragClones.push(clone(multiDragElements[i])); + multiDragClones[i].sortableIndex = multiDragElements[i].sortableIndex; + multiDragClones[i].draggable = false; + multiDragClones[i].style['will-change'] = ''; + toggleClass(multiDragClones[i], this.options.selectedClass, false); + multiDragElements[i] === dragEl$1 && toggleClass(multiDragClones[i], this.options.chosenClass, false); + } + + sortable._hideClone(); + + cancel(); + }, + clone: function clone(_ref3) { + var sortable = _ref3.sortable, + rootEl = _ref3.rootEl, + dispatchSortableEvent = _ref3.dispatchSortableEvent, + cancel = _ref3.cancel; + if (!this.isMultiDrag) return; + + if (!this.options.removeCloneOnHide) { + if (multiDragElements.length && multiDragSortable === sortable) { + insertMultiDragClones(true, rootEl); + dispatchSortableEvent('clone'); + cancel(); + } + } + }, + showClone: function showClone(_ref4) { + var cloneNowShown = _ref4.cloneNowShown, + rootEl = _ref4.rootEl, + cancel = _ref4.cancel; + if (!this.isMultiDrag) return; + insertMultiDragClones(false, rootEl); + multiDragClones.forEach(function (clone) { + css(clone, 'display', ''); + }); + cloneNowShown(); + clonesHidden = false; + cancel(); + }, + hideClone: function hideClone(_ref5) { + var _this = this; + + var sortable = _ref5.sortable, + cloneNowHidden = _ref5.cloneNowHidden, + cancel = _ref5.cancel; + if (!this.isMultiDrag) return; + multiDragClones.forEach(function (clone) { + css(clone, 'display', 'none'); + + if (_this.options.removeCloneOnHide && clone.parentNode) { + clone.parentNode.removeChild(clone); + } + }); + cloneNowHidden(); + clonesHidden = true; + cancel(); + }, + dragStartGlobal: function dragStartGlobal(_ref6) { + var sortable = _ref6.sortable; + + if (!this.isMultiDrag && multiDragSortable) { + multiDragSortable.multiDrag._deselectMultiDrag(); + } + + multiDragElements.forEach(function (multiDragElement) { + multiDragElement.sortableIndex = index(multiDragElement); + }); // Sort multi-drag elements + + multiDragElements = multiDragElements.sort(function (a, b) { + return a.sortableIndex - b.sortableIndex; + }); + dragStarted = true; + }, + dragStarted: function dragStarted(_ref7) { + var _this2 = this; + + var sortable = _ref7.sortable; + if (!this.isMultiDrag) return; + + if (this.options.sort) { + // Capture rects, + // hide multi drag elements (by positioning them absolute), + // set multi drag elements rects to dragRect, + // show multi drag elements, + // animate to rects, + // unset rects & remove from DOM + sortable.captureAnimationState(); + + if (this.options.animation) { + multiDragElements.forEach(function (multiDragElement) { + if (multiDragElement === dragEl$1) return; + css(multiDragElement, 'position', 'absolute'); + }); + var dragRect = getRect(dragEl$1, false, true, true); + multiDragElements.forEach(function (multiDragElement) { + if (multiDragElement === dragEl$1) return; + setRect(multiDragElement, dragRect); + }); + folding = true; + initialFolding = true; + } + } + + sortable.animateAll(function () { + folding = false; + initialFolding = false; + + if (_this2.options.animation) { + multiDragElements.forEach(function (multiDragElement) { + unsetRect(multiDragElement); + }); + } // Remove all auxiliary multidrag items from el, if sorting enabled + + + if (_this2.options.sort) { + removeMultiDragElements(); + } + }); + }, + dragOver: function dragOver(_ref8) { + var target = _ref8.target, + completed = _ref8.completed, + cancel = _ref8.cancel; + + if (folding && ~multiDragElements.indexOf(target)) { + completed(false); + cancel(); + } + }, + revert: function revert(_ref9) { + var fromSortable = _ref9.fromSortable, + rootEl = _ref9.rootEl, + sortable = _ref9.sortable, + dragRect = _ref9.dragRect; + + if (multiDragElements.length > 1) { + // Setup unfold animation + multiDragElements.forEach(function (multiDragElement) { + sortable.addAnimationState({ + target: multiDragElement, + rect: folding ? getRect(multiDragElement) : dragRect + }); + unsetRect(multiDragElement); + multiDragElement.fromRect = dragRect; + fromSortable.removeAnimationState(multiDragElement); + }); + folding = false; + insertMultiDragElements(!this.options.removeCloneOnHide, rootEl); + } + }, + dragOverCompleted: function dragOverCompleted(_ref10) { + var sortable = _ref10.sortable, + isOwner = _ref10.isOwner, + insertion = _ref10.insertion, + activeSortable = _ref10.activeSortable, + parentEl = _ref10.parentEl, + putSortable = _ref10.putSortable; + var options = this.options; + + if (insertion) { + // Clones must be hidden before folding animation to capture dragRectAbsolute properly + if (isOwner) { + activeSortable._hideClone(); + } + + initialFolding = false; // If leaving sort:false root, or already folding - Fold to new location + + if (options.animation && multiDragElements.length > 1 && (folding || !isOwner && !activeSortable.options.sort && !putSortable)) { + // Fold: Set all multi drag elements's rects to dragEl's rect when multi-drag elements are invisible + var dragRectAbsolute = getRect(dragEl$1, false, true, true); + multiDragElements.forEach(function (multiDragElement) { + if (multiDragElement === dragEl$1) return; + setRect(multiDragElement, dragRectAbsolute); // Move element(s) to end of parentEl so that it does not interfere with multi-drag clones insertion if they are inserted + // while folding, and so that we can capture them again because old sortable will no longer be fromSortable + + parentEl.appendChild(multiDragElement); + }); + folding = true; + } // Clones must be shown (and check to remove multi drags) after folding when interfering multiDragElements are moved out + + + if (!isOwner) { + // Only remove if not folding (folding will remove them anyways) + if (!folding) { + removeMultiDragElements(); + } + + if (multiDragElements.length > 1) { + var clonesHiddenBefore = clonesHidden; + + activeSortable._showClone(sortable); // Unfold animation for clones if showing from hidden + + + if (activeSortable.options.animation && !clonesHidden && clonesHiddenBefore) { + multiDragClones.forEach(function (clone) { + activeSortable.addAnimationState({ + target: clone, + rect: clonesFromRect + }); + clone.fromRect = clonesFromRect; + clone.thisAnimationDuration = null; + }); + } + } else { + activeSortable._showClone(sortable); + } + } + } + }, + dragOverAnimationCapture: function dragOverAnimationCapture(_ref11) { + var dragRect = _ref11.dragRect, + isOwner = _ref11.isOwner, + activeSortable = _ref11.activeSortable; + multiDragElements.forEach(function (multiDragElement) { + multiDragElement.thisAnimationDuration = null; + }); + + if (activeSortable.options.animation && !isOwner && activeSortable.multiDrag.isMultiDrag) { + clonesFromRect = _extends({}, dragRect); + var dragMatrix = matrix(dragEl$1, true); + clonesFromRect.top -= dragMatrix.f; + clonesFromRect.left -= dragMatrix.e; + } + }, + dragOverAnimationComplete: function dragOverAnimationComplete() { + if (folding) { + folding = false; + removeMultiDragElements(); + } + }, + drop: function drop(_ref12) { + var evt = _ref12.originalEvent, + rootEl = _ref12.rootEl, + parentEl = _ref12.parentEl, + sortable = _ref12.sortable, + dispatchSortableEvent = _ref12.dispatchSortableEvent, + oldIndex = _ref12.oldIndex, + putSortable = _ref12.putSortable; + var toSortable = putSortable || this.sortable; + if (!evt) return; + var options = this.options, + children = parentEl.children; // Multi-drag selection + + if (!dragStarted) { + if (options.multiDragKey && !this.multiDragKeyDown) { + this._deselectMultiDrag(); + } + + toggleClass(dragEl$1, options.selectedClass, !~multiDragElements.indexOf(dragEl$1)); + + if (!~multiDragElements.indexOf(dragEl$1)) { + multiDragElements.push(dragEl$1); + dispatchEvent({ + sortable: sortable, + rootEl: rootEl, + name: 'select', + targetEl: dragEl$1, + originalEvt: evt + }); // Modifier activated, select from last to dragEl + + if (evt.shiftKey && lastMultiDragSelect && sortable.el.contains(lastMultiDragSelect)) { + var lastIndex = index(lastMultiDragSelect), + currentIndex = index(dragEl$1); + + if (~lastIndex && ~currentIndex && lastIndex !== currentIndex) { + // Must include lastMultiDragSelect (select it), in case modified selection from no selection + // (but previous selection existed) + var n, i; + + if (currentIndex > lastIndex) { + i = lastIndex; + n = currentIndex; + } else { + i = currentIndex; + n = lastIndex + 1; + } + + for (; i < n; i++) { + if (~multiDragElements.indexOf(children[i])) continue; + toggleClass(children[i], options.selectedClass, true); + multiDragElements.push(children[i]); + dispatchEvent({ + sortable: sortable, + rootEl: rootEl, + name: 'select', + targetEl: children[i], + originalEvt: evt + }); + } + } + } else { + lastMultiDragSelect = dragEl$1; + } + + multiDragSortable = toSortable; + } else { + multiDragElements.splice(multiDragElements.indexOf(dragEl$1), 1); + lastMultiDragSelect = null; + dispatchEvent({ + sortable: sortable, + rootEl: rootEl, + name: 'deselect', + targetEl: dragEl$1, + originalEvt: evt + }); + } + } // Multi-drag drop + + + if (dragStarted && this.isMultiDrag) { + // Do not "unfold" after around dragEl if reverted + if ((parentEl[expando].options.sort || parentEl !== rootEl) && multiDragElements.length > 1) { + var dragRect = getRect(dragEl$1), + multiDragIndex = index(dragEl$1, ':not(.' + this.options.selectedClass + ')'); + if (!initialFolding && options.animation) dragEl$1.thisAnimationDuration = null; + toSortable.captureAnimationState(); + + if (!initialFolding) { + if (options.animation) { + dragEl$1.fromRect = dragRect; + multiDragElements.forEach(function (multiDragElement) { + multiDragElement.thisAnimationDuration = null; + + if (multiDragElement !== dragEl$1) { + var rect = folding ? getRect(multiDragElement) : dragRect; + multiDragElement.fromRect = rect; // Prepare unfold animation + + toSortable.addAnimationState({ + target: multiDragElement, + rect: rect + }); + } + }); + } // Multi drag elements are not necessarily removed from the DOM on drop, so to reinsert + // properly they must all be removed + + + removeMultiDragElements(); + multiDragElements.forEach(function (multiDragElement) { + if (children[multiDragIndex]) { + parentEl.insertBefore(multiDragElement, children[multiDragIndex]); + } else { + parentEl.appendChild(multiDragElement); + } + + multiDragIndex++; + }); // If initial folding is done, the elements may have changed position because they are now + // unfolding around dragEl, even though dragEl may not have his index changed, so update event + // must be fired here as Sortable will not. + + if (oldIndex === index(dragEl$1)) { + var update = false; + multiDragElements.forEach(function (multiDragElement) { + if (multiDragElement.sortableIndex !== index(multiDragElement)) { + update = true; + return; + } + }); + + if (update) { + dispatchSortableEvent('update'); + } + } + } // Must be done after capturing individual rects (scroll bar) + + + multiDragElements.forEach(function (multiDragElement) { + unsetRect(multiDragElement); + }); + toSortable.animateAll(); + } + + multiDragSortable = toSortable; + } // Remove clones if necessary + + + if (rootEl === parentEl || putSortable && putSortable.lastPutMode !== 'clone') { + multiDragClones.forEach(function (clone) { + clone.parentNode && clone.parentNode.removeChild(clone); + }); + } + }, + nullingGlobal: function nullingGlobal() { + this.isMultiDrag = dragStarted = false; + multiDragClones.length = 0; + }, + destroyGlobal: function destroyGlobal() { + this._deselectMultiDrag(); + + off(document, 'pointerup', this._deselectMultiDrag); + off(document, 'mouseup', this._deselectMultiDrag); + off(document, 'touchend', this._deselectMultiDrag); + off(document, 'keydown', this._checkKeyDown); + off(document, 'keyup', this._checkKeyUp); + }, + _deselectMultiDrag: function _deselectMultiDrag(evt) { + if (typeof dragStarted !== "undefined" && dragStarted) return; // Only deselect if selection is in this sortable + + if (multiDragSortable !== this.sortable) return; // Only deselect if target is not item in this sortable + + if (evt && closest(evt.target, this.options.draggable, this.sortable.el, false)) return; // Only deselect if left click + + if (evt && evt.button !== 0) return; + + while (multiDragElements.length) { + var el = multiDragElements[0]; + toggleClass(el, this.options.selectedClass, false); + multiDragElements.shift(); + dispatchEvent({ + sortable: this.sortable, + rootEl: this.sortable.el, + name: 'deselect', + targetEl: el, + originalEvt: evt + }); + } + }, + _checkKeyDown: function _checkKeyDown(evt) { + if (evt.key === this.options.multiDragKey) { + this.multiDragKeyDown = true; + } + }, + _checkKeyUp: function _checkKeyUp(evt) { + if (evt.key === this.options.multiDragKey) { + this.multiDragKeyDown = false; + } + } + }; + return _extends(MultiDrag, { + // Static methods & properties + pluginName: 'multiDrag', + utils: { + /** + * Selects the provided multi-drag item + * @param {HTMLElement} el The element to be selected + */ + select: function select(el) { + var sortable = el.parentNode[expando]; + if (!sortable || !sortable.options.multiDrag || ~multiDragElements.indexOf(el)) return; + + if (multiDragSortable && multiDragSortable !== sortable) { + multiDragSortable.multiDrag._deselectMultiDrag(); + + multiDragSortable = sortable; + } + + toggleClass(el, sortable.options.selectedClass, true); + multiDragElements.push(el); + }, + + /** + * Deselects the provided multi-drag item + * @param {HTMLElement} el The element to be deselected + */ + deselect: function deselect(el) { + var sortable = el.parentNode[expando], + index = multiDragElements.indexOf(el); + if (!sortable || !sortable.options.multiDrag || !~index) return; + toggleClass(el, sortable.options.selectedClass, false); + multiDragElements.splice(index, 1); + } + }, + eventProperties: function eventProperties() { + var _this3 = this; + + var oldIndicies = [], + newIndicies = []; + multiDragElements.forEach(function (multiDragElement) { + oldIndicies.push({ + multiDragElement: multiDragElement, + index: multiDragElement.sortableIndex + }); // multiDragElements will already be sorted if folding + + var newIndex; + + if (folding && multiDragElement !== dragEl$1) { + newIndex = -1; + } else if (folding) { + newIndex = index(multiDragElement, ':not(.' + _this3.options.selectedClass + ')'); + } else { + newIndex = index(multiDragElement); + } + + newIndicies.push({ + multiDragElement: multiDragElement, + index: newIndex + }); + }); + return { + items: _toConsumableArray(multiDragElements), + clones: [].concat(multiDragClones), + oldIndicies: oldIndicies, + newIndicies: newIndicies + }; + }, + optionListeners: { + multiDragKey: function multiDragKey(key) { + key = key.toLowerCase(); + + if (key === 'ctrl') { + key = 'Control'; + } else if (key.length > 1) { + key = key.charAt(0).toUpperCase() + key.substr(1); + } + + return key; + } + } + }); + } + + function insertMultiDragElements(clonesInserted, rootEl) { + multiDragElements.forEach(function (multiDragElement, i) { + var target = rootEl.children[multiDragElement.sortableIndex + (clonesInserted ? Number(i) : 0)]; + + if (target) { + rootEl.insertBefore(multiDragElement, target); + } else { + rootEl.appendChild(multiDragElement); + } + }); + } + /** + * Insert multi-drag clones + * @param {[Boolean]} elementsInserted Whether the multi-drag elements are inserted + * @param {HTMLElement} rootEl + */ + + + function insertMultiDragClones(elementsInserted, rootEl) { + multiDragClones.forEach(function (clone, i) { + var target = rootEl.children[clone.sortableIndex + (elementsInserted ? Number(i) : 0)]; + + if (target) { + rootEl.insertBefore(clone, target); + } else { + rootEl.appendChild(clone); + } + }); + } + + function removeMultiDragElements() { + multiDragElements.forEach(function (multiDragElement) { + if (multiDragElement === dragEl$1) return; + multiDragElement.parentNode && multiDragElement.parentNode.removeChild(multiDragElement); + }); + } + + Sortable.mount(new AutoScrollPlugin()); + Sortable.mount(Remove, Revert); + + Sortable.mount(new SwapPlugin()); + Sortable.mount(new MultiDragPlugin()); + + return Sortable; + +})); diff --git a/js/tools/_all.js b/js/tools/_all.js new file mode 100644 index 0000000..e250549 --- /dev/null +++ b/js/tools/_all.js @@ -0,0 +1,68 @@ +new Tool('eraser', { + cursor: 'none', + brushPreview: true, +}); +new Tool('resizeeraser', { + cursor: 'default', +}); + +new Tool('eyedropper', { + imageCursor: 'eyedropper', +}); + +new Tool('fill', { + imageCursor: 'fill', +}); + +new Tool('line', { + cursor: 'none', + brushPreview: true, +}); +new Tool('resizeline', { + cursor: 'default', +}); + +new Tool('pan', { + cursor: function () { + if (dragging) return 'url(\'/pixel-editor/pan-held.png\'), auto'; + else return 'url(\'/pixel-editor/pan.png\'), auto'; + }, +}); + +new Tool('pencil', { + cursor: 'none', + brushPreview: true, +}); +new Tool('resizebrush', { + cursor: 'default', +}); + +new Tool('rectangle', { + cursor: 'none', + brushPreview: true, +}); +new Tool('ellipse', { + cursor: 'none', + brushPreview: true, +}); +new Tool('resizerectangle', { + cursor: 'default', +}); + +new Tool('rectselect', { + cursor: 'crosshair', + brushPreview: true, +}); + + +new Tool('moveselection', { + cursor: 'crosshair', +}); + +new Tool('zoom', { + imageCursor: 'zoom-in', +}); + +//set a default tool +var currentTool = tool.pencil; +var currentToolTemp = tool.pencil; \ No newline at end of file diff --git a/js/tools/_eraser.js b/js/tools/_eraser.js deleted file mode 100644 index fd888fb..0000000 --- a/js/tools/_eraser.js +++ /dev/null @@ -1,13 +0,0 @@ - -new Tool('eraser', { - cursor: 'crosshair', - brushPreview: true, -}); - - - -new Tool('resizeeraser', { - cursor: 'default', -}); - -/*global Tool, tool*/ \ No newline at end of file diff --git a/js/tools/_eyedropper.js b/js/tools/_eyedropper.js deleted file mode 100644 index cf1b0b6..0000000 --- a/js/tools/_eyedropper.js +++ /dev/null @@ -1,7 +0,0 @@ - -new Tool('eyedropper', { - imageCursor: 'eyedropper', -}); - - -/*global Tool, tool*/ \ No newline at end of file diff --git a/js/tools/_fill.js b/js/tools/_fill.js deleted file mode 100644 index 090a38e..0000000 --- a/js/tools/_fill.js +++ /dev/null @@ -1,7 +0,0 @@ - -new Tool('fill', { - imageCursor: 'fill', -}); - - -/*global Tool, tool*/ \ No newline at end of file diff --git a/js/tools/_pan.js b/js/tools/_pan.js deleted file mode 100644 index ead4854..0000000 --- a/js/tools/_pan.js +++ /dev/null @@ -1,10 +0,0 @@ - -new Tool('pan', { - cursor: function () { - if (dragging) return 'url(\'/pixel-editor/pan-held.png\'), auto'; - else return 'url(\'/pixel-editor/pan.png\'), auto'; - }, -}); - - -/*global Tool, tool*/ \ No newline at end of file diff --git a/js/tools/_pencil.js b/js/tools/_pencil.js deleted file mode 100644 index 707c177..0000000 --- a/js/tools/_pencil.js +++ /dev/null @@ -1,17 +0,0 @@ - -new Tool('pencil', { - cursor: 'crosshair', - brushPreview: true, -}); - - -new Tool('resizebrush', { - cursor: 'default', -}); - - -//set as default tool -var currentTool = tool.pencil; -var currentToolTemp = tool.pencil; - -/*global Tool, tool*/ \ No newline at end of file diff --git a/js/tools/_rectangle.js b/js/tools/_rectangle.js deleted file mode 100644 index 677bccd..0000000 --- a/js/tools/_rectangle.js +++ /dev/null @@ -1,14 +0,0 @@ - -new Tool('rectangle', { - cursor: 'crosshair', - brushPreview: true, -}); - - - -new Tool('resizerectangle', { - cursor: 'default', -}); - - -/*global Tool, tool*/ \ No newline at end of file diff --git a/js/tools/_select.js b/js/tools/_select.js deleted file mode 100644 index 89052d5..0000000 --- a/js/tools/_select.js +++ /dev/null @@ -1,14 +0,0 @@ - - -new Tool('rectselect', { - cursor: 'crosshair', - brushPreview: true, -}); - - -new Tool('moveselection', { - cursor: 'crosshair', -}); - - -/*global Tool, tool*/ \ No newline at end of file diff --git a/js/tools/_zoom.js b/js/tools/_zoom.js deleted file mode 100644 index b3e5c72..0000000 --- a/js/tools/_zoom.js +++ /dev/null @@ -1,6 +0,0 @@ - -new Tool('zoom', { - imageCursor: 'zoom-in', -}); - -/*global Tool, tool*/ \ No newline at end of file diff --git a/logs/latestLog.html b/logs/latestLog.html new file mode 100644 index 0000000..9871410 --- /dev/null +++ b/logs/latestLog.html @@ -0,0 +1,66 @@ +

Latest update

+Hello there, welcome to the latest version of the Lospec Pixel Editor. As you can see, we changed +quite a lot of things. Let's go through all them, starting from this page. + +

Splash page

+ +The editor now has a splash page! Besides a fancy cover image with beautiful art, on the bottom +left of the page you'll be able to create a new custom pixel. You can also use the quickstart +menu to quickly select a preset or load an existing file. It was designed by Skeddles himself! + + + +Pro tip: once you've created a new project, you can go back to the splash page +by clicking on Editor -> Splash page + +

Canvas resizing

+ +We implemented canvas resizing! If you wanted to draw a t-rex but created a 4x4 canvas, we've got you covered! +You can now click on Edit -> Resize canvas to decrease the size of the project. Same goes if you +drew an ant on a 1024x1024 canvas, just go to Edit -> Resize canvas and decrease +the dimensions. + + + +

Sprite scaling

+ +In addition to the Lospec Pixel Art Scaler, +you can now take advantage of the editor's built-in scaling function. Just click on Edit -> Scale sprite +to scale up or down your work. With the nearest-neighbour algorithm you'll be able to scale sprites +in a pixel-perfect manner, while with bilinear interpolation it's possible to add (or remove, if you're scaling +down a sprite) antialiasing. + + + +

Line tool

+ +Our contributor Liam added a new line tool! Quality of +life improvement are planned for it, the rectangle and the rectangular selection tools. + + + +

Advanced mode: colour picker and palette block

+ +If you're a proud user of the advanced mode, you'll be able to try out the new colour picker: it +supports 3 colour models, 6 colour harmonies and multiple ways to input data. Next to it, +you'll find the new palette block, which lets you arrange your colours however you want, add and +remove multiple colours at once. Changes made in the palette block will update the palette list +you've always been familiar with. + + + +

Other changes:

+
    +
  • You can now move colours in the palette menu
  • +
  • Use View -> Pixel grid to show the pixel grid
  • +
  • Fixed a bunch of bugs, made the brush preview pixel perfect
  • +
  • Added Layer -> Duplicate to duplicate a layer
  • +
  • Quality of life development improvements by Nkoder and Pongles
  • +
  • Canvas trimming to get rid of all the extra space you have in your sprite
  • +
+ +

That's all folks!

+That's all for this update! Hope you have fun with this new release :)
+- Unsettled +

+P.S.: we're always looking for contributors! Join the Lospec discord to get in touch! \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index da40627..d9a02a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,62 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" + }, + "@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "@tokenizer/token": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.1.1.tgz", + "integrity": "sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w==" + }, + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" + }, + "@types/minimist": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", + "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==" + }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -34,6 +90,39 @@ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, + "ansi-align": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "requires": { + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "ansi-colors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", @@ -258,6 +347,11 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -450,6 +544,104 @@ } } }, + "boxen": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "cli-boxes": "^2.2.0", + "string-width": "^4.1.0", + "term-size": "^2.1.0", + "type-fest": "^0.8.1", + "widest-line": "^3.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -522,6 +714,44 @@ "unset-value": "^1.0.0" } }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, "callsites": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/callsites/-/callsites-1.0.1.tgz", @@ -590,6 +820,11 @@ } } }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -611,6 +846,33 @@ } } }, + "cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" + }, + "cli-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.0.tgz", + "integrity": "sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A==", + "dev": true, + "requires": { + "ansi-regex": "^2.1.1", + "d": "^1.0.1", + "es5-ext": "^0.10.51", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.14", + "timers-ext": "^0.1.7" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, "cli-columns": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/cli-columns/-/cli-columns-3.1.2.tgz", @@ -660,6 +922,14 @@ "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=" }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, "clone-stats": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", @@ -757,8 +1027,7 @@ "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "optional": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "component-emitter": { "version": "1.3.0", @@ -810,6 +1079,199 @@ } } }, + "concurrently": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.0.2.tgz", + "integrity": "sha512-u+1Q0dJG5BidgUTpz9CU16yoHTt/oApFDQ3mbvHwSDgMjU7aGqy0q8ZQyaZyaNxdwRKTD872Ux3Twc6//sWA+Q==", + "requires": { + "chalk": "^4.1.0", + "date-fns": "^2.16.1", + "lodash": "^4.17.21", + "read-pkg": "^5.2.0", + "rxjs": "^6.6.3", + "spawn-command": "^0.0.2-1", + "supports-color": "^8.1.0", + "tree-kill": "^1.2.2", + "yargs": "^16.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==" + } + } + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -879,6 +1341,37 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "cross-spawn": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", @@ -888,6 +1381,11 @@ "which": "^1.2.9" } }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -913,6 +1411,11 @@ "assert-plus": "^1.0.0" } }, + "date-fns": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.21.1.tgz", + "integrity": "sha512-m1WR0xGiC6j6jNFAyW4Nvh4WxAi4JF4w9jRJwSI8nBmNcyZXPcP9VUQG+6gHQXAmqaGEKDKhOqAtENDC941UkA==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -926,11 +1429,33 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + } + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, "default-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", @@ -951,6 +1476,16 @@ "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=" }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -1021,11 +1556,24 @@ "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=" }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "requires": { + "is-obj": "^2.0.0" + } + }, "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -1089,6 +1637,11 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -1150,6 +1703,16 @@ "es6-symbol": "^3.1.1" } }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1165,6 +1728,16 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "event-stream": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz", @@ -1392,6 +1965,17 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "file-type": { + "version": "14.7.1", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-14.7.1.tgz", + "integrity": "sha512-sXAMgFk67fQLcetXustxfKX+PZgHIUFn96Xld9uH8aXPdX3xOp0/jg9OdouVTvQrf7mrn+wAa4jN/y9fUOOiRA==", + "requires": { + "readable-web-to-node-stream": "^2.0.0", + "strtok3": "^6.0.3", + "token-types": "^2.0.0", + "typedarray-to-buffer": "^3.1.5" + } + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -2189,6 +2773,25 @@ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -2283,6 +2886,14 @@ "object.defaults": "^1.1.0" } }, + "global-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", + "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "requires": { + "ini": "1.3.7" + } + }, "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", @@ -2335,6 +2946,24 @@ "sparkles": "^1.0.0" } }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, "graceful-fs": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", @@ -2506,8 +3135,8 @@ } }, "handlebars-helper-svg": { - "version": "git+https://bitbucket.org/skeddles/npm-handlebars-helper-svg-lospec-open-source.git#2feeec5000aecce96ba2f714ec540880537ae208", - "from": "git+https://bitbucket.org/skeddles/npm-handlebars-helper-svg-lospec-open-source.git", + "version": "git+ssh://git@bitbucket.org/skeddles/npm-handlebars-helper-svg-lospec-open-source.git#2feeec5000aecce96ba2f714ec540880537ae208", + "from": "handlebars-helper-svg@git+https://bitbucket.org/skeddles/npm-handlebars-helper-svg-lospec-open-source.git", "requires": { "ltx": "^2.3.0", "resolve": "^1.1.7" @@ -2536,6 +3165,11 @@ "har-schema": "^2.0.0" } }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==" + }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -2595,6 +3229,11 @@ } } }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==" + }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -2608,6 +3247,11 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -2645,6 +3289,26 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, "in-publish": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz", @@ -2673,9 +3337,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==" }, "interpret": { "version": "1.2.0", @@ -2737,6 +3401,14 @@ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "requires": { + "ci-info": "^2.0.0" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -2772,6 +3444,11 @@ } } }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" + }, "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", @@ -2803,11 +3480,25 @@ "is-extglob": "^2.1.0" } }, + "is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "requires": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + } + }, "is-negated-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=" }, + "is-npm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==" + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -2826,6 +3517,21 @@ } } }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -2834,6 +3540,12 @@ "isobject": "^3.0.1" } }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -2842,6 +3554,11 @@ "is-unc-path": "^1.0.0" } }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -2871,9 +3588,17 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" }, "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } + }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" }, "isarray": { "version": "1.0.0", @@ -2900,11 +3625,26 @@ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz", "integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==" }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -2949,6 +3689,14 @@ "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=" }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "requires": { + "json-buffer": "3.0.0" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -2963,6 +3711,14 @@ "es6-weak-map": "^2.0.1" } }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "requires": { + "package-json": "^6.3.0" + } + }, "lazystream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", @@ -3031,6 +3787,11 @@ "resolve": "^1.1.7" } }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -3043,10 +3804,18 @@ "strip-bom": "^2.0.0" } }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.clonedeep": { "version": "4.5.0", @@ -3062,6 +3831,11 @@ "signal-exit": "^3.0.0" } }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", @@ -3071,6 +3845,15 @@ "yallist": "^2.1.2" } }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "dev": true, + "requires": { + "es5-ext": "~0.10.2" + } + }, "ltx": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/ltx/-/ltx-2.9.2.tgz", @@ -3079,6 +3862,21 @@ "inherits": "^2.0.4" } }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -3139,6 +3937,30 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, + "memoizee": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", + "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "dev": true, + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + }, + "dependencies": { + "next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true + } + } + }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", @@ -3204,6 +4026,16 @@ "mime-db": "1.43.0" } }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -3217,6 +4049,16 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + } + }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -3356,6 +4198,139 @@ } } }, + "nodemon": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz", + "integrity": "sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA==", + "requires": { + "chokidar": "^3.2.2", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.3", + "update-notifier": "^4.1.0" + }, + "dependencies": { + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + } + } + }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -3383,6 +4358,11 @@ "remove-trailing-separator": "^1.0.1" } }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + }, "now-and-later": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", @@ -3523,11 +4503,175 @@ } }, "open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/open/-/open-8.0.6.tgz", + "integrity": "sha512-vDOC0KwGabMPFtIpCO2QOnQeOz0N2rEkbuCuxICwLMUCrpv+A7NHrrzJ2dQReJmVluHhO4pYRh/Pn6s8t7Op6Q==", "requires": { - "is-wsl": "^1.1.0" + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "open-cli": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/open-cli/-/open-cli-6.0.1.tgz", + "integrity": "sha512-A5h8MF3GrT1efn9TiO9LPajDnLtuEiGQT5G8TxWObBlgt1cZJF1YbQo/kNtsD1bJb7HxnT6SaSjzeLq0Rfhygw==", + "requires": { + "file-type": "^14.1.4", + "get-stdin": "^7.0.0", + "meow": "^6.1.0", + "open": "^7.0.3", + "temp-write": "^4.0.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "get-stdin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==" + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + }, + "map-obj": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", + "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==" + }, + "meow": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-6.1.1.tgz", + "integrity": "sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==", + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "^4.0.2", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + }, + "dependencies": { + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==" + } + } + }, + "open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "requires": { + "min-indent": "^1.0.0" + } + }, + "trim-newlines": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", + "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==" + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } } }, "ordered-read-streams": { @@ -3594,6 +4738,50 @@ "os-tmpdir": "^1.0.0" } }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "parent-module": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-0.1.0.tgz", @@ -3658,6 +4846,12 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", @@ -3699,11 +4893,21 @@ "through": "~2.3" } }, + "peek-readable": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-3.1.3.tgz", + "integrity": "sha512-mpAcysyRJxmICBcBa5IXH7SZPvWkcghm6Fk8RekoS3v+BpbSzlZzuWbMx+GXrlUwESi9qHar4nVEZNMKylIHvg==" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "picomatch": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==" + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -3738,6 +4942,11 @@ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -3767,6 +4976,11 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, "pump": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", @@ -3791,11 +5005,30 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, + "pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "requires": { + "escape-goat": "^2.0.0" + } + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -3812,6 +5045,17 @@ "unpipe": "1.0.0" } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -3841,6 +5085,11 @@ "util-deprecate": "^1.0.1" } }, + "readable-web-to-node-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-2.0.0.tgz", + "integrity": "sha512-+oZJurc4hXpaaqsN68GoZGQAQIA3qr09Or4fqEsargABnbe5Aau8hFn6ISVleT3cpY/0n/8drn7huyyEvTbghA==" + }, "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -3906,6 +5155,57 @@ "safe-regex": "^1.1.0" } }, + "registry-auth-token": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", + "requires": { + "rc": "^1.2.8" + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "requires": { + "rc": "^1.2.8" + } + }, + "reload": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/reload/-/reload-3.1.1.tgz", + "integrity": "sha512-JblFn8P8CUfKqpclWNGs6rhda4hmywTq/a8DyjmwoGZ7Lp2krZ3swXgU3guvZ0waI3rorXFVS6z6UkKLYulmMA==", + "dev": true, + "requires": { + "cli-color": "~2.0.0", + "commander": "~6.1.0", + "finalhandler": "~1.1.1", + "minimist": "~1.2.0", + "open": "^7.0.0", + "serve-static": "~1.14.0", + "supervisor": "~0.12.0", + "url-parse": "~1.4.4", + "ws": "~7.3.0" + }, + "dependencies": { + "commander": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.1.0.tgz", + "integrity": "sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA==", + "dev": true + }, + "open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + } + } + }, "remove-bom-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", @@ -4057,6 +5357,12 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, "resolve": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", @@ -4087,6 +5393,14 @@ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "requires": { + "lowercase-keys": "^1.0.0" + } + }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -4100,6 +5414,14 @@ "glob": "^7.1.3" } }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", @@ -4161,6 +5483,21 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "semver-greatest-satisfied-range": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", @@ -4243,6 +5580,21 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -4382,6 +5734,11 @@ "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==" }, + "spawn-command": { + "version": "0.0.2-1", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", + "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=" + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -4585,6 +5942,27 @@ "get-stdin": "^4.0.1" } }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "strtok3": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.0.8.tgz", + "integrity": "sha512-QLgv+oiXwXgCgp2PdPPa+Jpp4D9imK9e/0BsyfeFMr6QL6wMVqoVn9+OXQ9I7MZbmUzN6lmitTJ09uwS2OmGcw==", + "requires": { + "@tokenizer/token": "^0.1.1", + "@types/debug": "^4.1.5", + "peek-readable": "^3.1.3" + } + }, + "supervisor": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/supervisor/-/supervisor-0.12.0.tgz", + "integrity": "sha1-3n5jNwFbKRhRwQ81OMSn8EkX7ME=", + "dev": true + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -4612,6 +5990,28 @@ "inherits": "2" } }, + "temp-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", + "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=" + }, + "temp-write": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/temp-write/-/temp-write-4.0.0.tgz", + "integrity": "sha512-HIeWmj77uOOHb0QX7siN3OtwV3CTntquin6TNVg6SHOqCP3hYKmox90eeFOGaY1MqJ9WYDDjkyZrW6qS5AWpbw==", + "requires": { + "graceful-fs": "^4.1.15", + "is-stream": "^2.0.0", + "make-dir": "^3.0.0", + "temp-dir": "^1.0.0", + "uuid": "^3.3.2" + } + }, + "term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==" + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -4677,6 +6077,16 @@ "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "dev": true, + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, "to-absolute-glob": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", @@ -4704,6 +6114,11 @@ } } }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" + }, "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", @@ -4775,6 +6190,33 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "token-types": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-2.1.1.tgz", + "integrity": "sha512-wnQcqlreS6VjthyHO3Y/kpK/emflxDBNhlNUPfh7wE39KnuDdOituXomIbyI79vBtF0Ninpkh72mcuRHo+RG3Q==", + "requires": { + "@tokenizer/token": "^0.1.1", + "ieee754": "^1.2.1" + } + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "requires": { + "nopt": "~1.0.10" + }, + "dependencies": { + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "requires": { + "abbrev": "1" + } + } + } + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -4784,6 +6226,11 @@ "punycode": "^2.1.1" } }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" + }, "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", @@ -4797,6 +6244,11 @@ "glob": "^7.1.2" } }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -4815,6 +6267,11 @@ "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -4829,6 +6286,14 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, "uglify-js": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.1.tgz", @@ -4844,6 +6309,14 @@ "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" }, + "undefsafe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", + "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", + "requires": { + "debug": "^2.2.0" + } + }, "undertaker": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", @@ -4892,6 +6365,14 @@ "through2-filter": "^3.0.0" } }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "requires": { + "crypto-random-string": "^2.0.0" + } + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -4943,6 +6424,71 @@ "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" }, + "update-notifier": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", + "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", + "requires": { + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.3.1", + "is-npm": "^4.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -4956,6 +6502,24 @@ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "requires": { + "prepend-http": "^2.0.0" + } + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -5117,6 +6681,16 @@ } } }, + "wait-cli": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wait-cli/-/wait-cli-1.0.0.tgz", + "integrity": "sha512-wgLh7ohQmdO3++nsrxIB5WbKe0X4hrunpyiW5djMKSrQyDJaV1Trm1b3m5NU6OG9fXlDWOdbMRbZ8DydfK0O3A==", + "dev": true, + "requires": { + "commander": "^2.9.0", + "glob": "^7.1.2" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -5138,6 +6712,49 @@ "string-width": "^1.0.2 || 2" } }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "requires": { + "string-width": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -5177,6 +6794,28 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "ws": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", + "dev": true + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 98a7f48..cd8a3bc 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,18 @@ "scripts": { "build": "node ./build.js ./build", "serve": "node ./server.js ./build 3000", - "test": "npm run build && npm run serve" + "test": "npm run build && npm run serve", + "hot": "concurrently \"nodemon --exec npm test\" \"await tcp localhost:3000 && open-cli http://localhost:3000\"", + "hot:reload": "cross-env RELOAD=yes npm run hot" }, "author": "Lospec", "license": "ISC", + "nodemonConfig": { + "ext": "js,hbs,scss", + "ignore": "build/" + }, "dependencies": { + "concurrently": "^6.0.2", "express": "^4.16.4", "fs-extra": "^7.0.1", "gulp": "^4.0.2", @@ -19,7 +26,14 @@ "gulp-rename": "^2.0.0", "gulp-sass": "^4.0.2", "handlebars-helper-svg": "git+https://bitbucket.org/skeddles/npm-handlebars-helper-svg-lospec-open-source.git", - "open": "^6.0.0", + "nodemon": "^2.0.7", + "open": "^8.0.6", + "open-cli": "^6.0.1", "sass": "^1.17.3" + }, + "devDependencies": { + "cross-env": "7.0.3", + "reload": "^3.1.1", + "wait-cli": "^1.0.0" } } diff --git a/server.js b/server.js index 465a0ae..e6167ff 100644 --- a/server.js +++ b/server.js @@ -1,37 +1,48 @@ const path = require('path'); const express = require('express'); - +const reload = require('reload'); const app = express(); const BUILDDIR = process.argv[2] || './build'; const PORT = process.argv[3] || 3000; //ROUTE - index.htm -app.get('/', (req, res) => { +app.get('/', (req, res) => { res.sendFile(path.join(__dirname, BUILDDIR, 'index.htm'), {}, function (err) { - if(err){ - console.log('error sending file',err); + if (err) { + console.log('error sending file', err); } else { - setTimeout(()=>{ + console.log("Server: Successfully served index.html"); + + /*setTimeout(()=>{ console.log('closing server'); - server.close(); + res.app.server.close(); process.exit(); - },1000*10); + },1000*10); */ } }); }); +// Better to show landing page rather than 404 on editor page reload +app.get('/pixel-editor/app', (req, res) => { + res.redirect('/'); +}) + //ROUTE - other files app.use(express.static(path.join(__dirname, BUILDDIR))); -//start server -var server = app.listen(PORT, () => { - console.log(`\nTemp server started at http://localhost:${PORT}!`); - //console.log('press ctrl+c to stop '); - - var opn = require('open'); - - // opens the url in the default browser - opn(`http://localhost:${PORT}`); -}); - +// "reload" module allows us to trigger webpage reload automatically on file changes, but inside pixel editor it also +// makes browser steal focus from any other window in order to ask user about unsaved changes. It might be quite +// intrusive so we decided to give option to choose preferred workflow. +if (process.env.RELOAD === "yes") { + reload(app).then(() => { + //start server + app.server = app.listen(PORT, () => { + console.log(`Web server listening on port ${PORT} (with reload module)`); + }) + }); +} else { + app.listen(PORT, () => { + console.log(`Web server listening on port ${PORT}`); + }) +} diff --git a/views/pixel-editor.hbs b/views/pixel-editor.hbs index 2c72b99..f7c5e5a 100644 --- a/views/pixel-editor.hbs +++ b/views/pixel-editor.hbs @@ -13,7 +13,6 @@ -

Warning: a modern, desktop, web browser is required to use this tool.

@@ -34,6 +33,7 @@ +
@@ -91,6 +91,7 @@
  • +
@@ -119,40 +120,46 @@
  • - +
  • + + + +
  • + + + +
  • +
  • -
  • - - - -
  • -
      - - {{! -
    • - }} -
      -
    • +
      • @@ -232,9 +239,9 @@ {{svg "adjust.svg" width="20" height="20" }}
  • -
    +
    -
    +

    New Pixel

    @@ -262,8 +269,76 @@
    + +
    +
    +
    +

    Latest updates

    +
    +
    + +
    + + +
    +
    +

    New Custom Pixel

    + +

    Editor mode

    +
    +
    +

    Basic

    +

    Advanced

    +
    +
    + +

    Size

    +
    + {{svg "x.svg" width="16" height="16" class="dimentions-x"}} +
    + +

    Palette

    + + + +
    Creating a new pixel will discard your current one.
    +
    + +
    +
    + +
    +
    + Quickstart +
    + +
    +

    Load

    +

    New Gameboy

    +

    New C64

    +

    New Pico8

    +

    New 16x16

    +

    New 32x32

    +

    New 64x64

    +

    New 128x128

    +

    New 256x256

    +

    New 512x512

    +
    +
    +
    +
    +
    + -
    +

    Scale sprite

    @@ -313,7 +388,7 @@
    -
    +

    Resize canvas

    @@ -371,6 +446,82 @@
    + + +
    + + +

    Edit palette

    + +
    +
    + + + + +
    + +
    + +
    +
    + + + +
    + +
    + + + +
    + +
    + + + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + #123456 +
    +
    + +
    + + + + + + +
    +
    +
    + +
    +
      +
    • +
    • +
    +
    + +
    + + +
    +
    +

    Help

    @@ -381,19 +532,38 @@

    Hotkeys

      -
    • Pencil: B or 1
    • -
    • Fill: F or 2
    • -
    • Eyedropper: E or 3
    • -
    • Pan: P or M or 4
    • -
    • Zoom: Z or 5
    • -
    • Undo: Ctrl + Z
    • -
    • Redo: Ctrl + Y or Ctrl + Alt + Z
    • +
    • Pencil: B or 1
    • +
    • Eraser: R
    • +
    • Rectangle: U
    • +
    • Line: L
    • +
    • Fill: F or 2
    • +
    • Eyedropper: E or 3
    • +
    • Pan: P or M or 4
    • +
    • Zoom: Z or 5
    • +
    • Undo: Ctrl + Z
    • +
    • Redo: Ctrl + Y or Ctrl + Alt + Z
    • +
    • Rectangular selection: M

    Mouse Shortcuts

      -
    • Alt + Click - Eyedropper
    • -
    • Space + Click - Pan
    • -
    • Alt + Scroll Wheel - Zoom
    • +
    • Eyedropper: Alt + Click
    • +
    • Pan: Space + Click
    • +
    • Zoom: Alt + Scroll Wheel
    • +
    +

    Layers

    +
      +
    • {{svg "visible.svg" width="15" height="15" class = "default-icon"}}: show / hide layer
    • +
    • {{svg "lockedpadlock.svg" width="15" height="15" class = "default-icon"}}: lock / unlock layer, when a layer is locked it's not possible to draw on it
    • +
    • Right click on a layer to open the menu: +
        +
      • Rename: change the name of the layer
      • +
      • Duplicate: duplicate the layer
      • +
      • Delete: delete the layer (doesn't work if there's only one layer)
      • +
      • Merge below: merges the selected the layer with the one below it
      • +
      • Flatten visible: merges all the visible layers
      • +
      • Flatten all: merges all the layers
      • +
      +
    @@ -481,5 +651,6 @@ + \ No newline at end of file