diff --git a/frontend/.babelrc b/frontend/.babelrc new file mode 100644 index 0000000..2b31862 --- /dev/null +++ b/frontend/.babelrc @@ -0,0 +1,8 @@ +{ + "presets": ["@babel/preset-env"], + "plugins": [ + ["@babel/plugin-transform-runtime", { + "regenerator": true + }] + ] +} diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..9df4bb1 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,17 @@ +# OS generated files # +.DS_Store +.DS_Store? +._* +.Trashes +ehthumbs.db +Thumbs.db +nbproject/ +.idea/ +.git/ +.vscode +/.project +*.log + +/node_modules/ +*.js.ignore + diff --git a/frontend/MIT-LICENSE.txt b/frontend/MIT-LICENSE.txt new file mode 100644 index 0000000..760e232 --- /dev/null +++ b/frontend/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +Copyright (c) ViliusL +https://github.com/viliusle + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..8f0f308 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,60 @@ +# miniPaint + +Online image editor lets you create and edit images using HTML5 technologies. No need to buy, download, install, or have outdated flash. No ads. Key features: layers, filters, open source Photoshop alternative. + +miniPaint operates directly in the browser. You can create images by pasting from the clipboard (ctrl+v) or uploading from the computer (_using menu or drag & drop_). Nothing will be sent to any server. Everything stays in your browser. + +## URL: +**https://viliusle.github.io/miniPaint/** + +## Preview: +![miniPaint](https://raw.githubusercontent.com/viliusle/miniPaint/master/images/preview.gif) +(generated using miniPaint) + +**Change log:** [/miniPaint/releases](https://github.com/viliusle/miniPaint/releases) + +## Browser Support +- Chrome +- Firefox +- Opera +- Edge +- Safari +- Yandex + +## Features + +**Files**: open images, directories, URLs, data URLs, drag and drop, save (PNG, JPG, BMP, WEBP, animated GIF, TIFF, JSON (layers data), print. + +**Edit**: undo, cut, copy, paste, selection, paste from the clipboard. + +**Image**: information, EXIF, trim, zoom, resize (Hermite resample, default resize), rotate, flip, color corrections (brightness, contrast, hue, saturation, luminance), automatic color adjustment, grid, histogram, negative. + +**Layers**: multi-layer system, differences, merging, flattening, transparency support. + +**Effects**: black and white, blur (box, gaussian, stack, zoom), bulge/pinch, denoise, desaturation, dither, dot screen, edge, emboss, enrich, gamma, grains, grayscale, heatmap, jpg compression, mosaic, oil, sepia, sharpen, solarize, tilt shift, vignette, vibrance, vintage, blueprint, night vision, pencil, also instagram filters: 1977, aden, clarendon, gingham, inkwell, lo-fi, toaster, valencia, x-pro ii. + +**Tools**: pencil, brush, magic wand, eraser, fill, color picker, letter, crop, blur, sharpener, desaturation, clone, borders, sprites, keypoints, color zoom, change color, restore transparency, content fill. + +**Help**: keyboard shortcuts, translation. + +## Embed +To embed this app on another page, use the following HTML code: + + + +## Build instructions +See [Wiki > Build instructions](https://github.com/viliusle/miniPaint/wiki/Build-instructions) + +## Wiki +See [Wiki](https://github.com/viliusle/miniPaint/wiki) + +## Contributors + + + + +## License +MIT License + +## Support +Please use the GitHub issues for support, feature requests and bug reports, or contact us by sending an email to www.viliusl@gmail.com. diff --git a/frontend/SECURITY.md b/frontend/SECURITY.md new file mode 100644 index 0000000..845ed38 --- /dev/null +++ b/frontend/SECURITY.md @@ -0,0 +1,15 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| latest | :white_check_mark: | +| < latest | :x: | + +## Reporting a Vulnerability + +Please send details to www.viliusl@gmail.com diff --git a/frontend/examples/add-edit-imgData.html b/frontend/examples/add-edit-imgData.html new file mode 100644 index 0000000..dae792d --- /dev/null +++ b/frontend/examples/add-edit-imgData.html @@ -0,0 +1,76 @@ + + + + + + \ No newline at end of file diff --git a/frontend/examples/open-edit-save.html b/frontend/examples/open-edit-save.html new file mode 100644 index 0000000..c82c6db --- /dev/null +++ b/frontend/examples/open-edit-save.html @@ -0,0 +1,128 @@ + + + + + +
+ Click on image to edit. +

+ + + + OR + + +

+ + + + + +
+ + \ No newline at end of file diff --git a/frontend/examples/zoom.html b/frontend/examples/zoom.html new file mode 100644 index 0000000..a057086 --- /dev/null +++ b/frontend/examples/zoom.html @@ -0,0 +1,71 @@ + + + + + + \ No newline at end of file diff --git a/frontend/images/favicon.png b/frontend/images/favicon.png new file mode 100644 index 0000000..bcd4645 Binary files /dev/null and b/frontend/images/favicon.png differ diff --git a/frontend/images/favicon.svg b/frontend/images/favicon.svg new file mode 100644 index 0000000..b861938 --- /dev/null +++ b/frontend/images/favicon.svg @@ -0,0 +1,13 @@ + + + + + + + diff --git a/frontend/images/icons/animation.svg b/frontend/images/icons/animation.svg new file mode 100644 index 0000000..c326d23 --- /dev/null +++ b/frontend/images/icons/animation.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/frontend/images/icons/arrow-down.svg b/frontend/images/icons/arrow-down.svg new file mode 100644 index 0000000..d2483ee --- /dev/null +++ b/frontend/images/icons/arrow-down.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/images/icons/blur.svg b/frontend/images/icons/blur.svg new file mode 100644 index 0000000..40a6408 --- /dev/null +++ b/frontend/images/icons/blur.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + diff --git a/frontend/images/icons/bold.svg b/frontend/images/icons/bold.svg new file mode 100644 index 0000000..8a31bcc --- /dev/null +++ b/frontend/images/icons/bold.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/images/icons/brush.svg b/frontend/images/icons/brush.svg new file mode 100644 index 0000000..c4fb3ad --- /dev/null +++ b/frontend/images/icons/brush.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/images/icons/bulge_pinch.svg b/frontend/images/icons/bulge_pinch.svg new file mode 100644 index 0000000..0fed9ab --- /dev/null +++ b/frontend/images/icons/bulge_pinch.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/frontend/images/icons/clone.svg b/frontend/images/icons/clone.svg new file mode 100644 index 0000000..af3ca66 --- /dev/null +++ b/frontend/images/icons/clone.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/frontend/images/icons/crop.svg b/frontend/images/icons/crop.svg new file mode 100644 index 0000000..dd9c5f0 --- /dev/null +++ b/frontend/images/icons/crop.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/frontend/images/icons/delete.svg b/frontend/images/icons/delete.svg new file mode 100644 index 0000000..6fc6fd9 --- /dev/null +++ b/frontend/images/icons/delete.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/images/icons/desaturate.svg b/frontend/images/icons/desaturate.svg new file mode 100644 index 0000000..5e21e11 --- /dev/null +++ b/frontend/images/icons/desaturate.svg @@ -0,0 +1,7 @@ + + + + + Svg Vector Icons : http://www.onlinewebfonts.com/icon + + \ No newline at end of file diff --git a/frontend/images/icons/erase.svg b/frontend/images/icons/erase.svg new file mode 100644 index 0000000..c2250bd --- /dev/null +++ b/frontend/images/icons/erase.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/frontend/images/icons/external.png b/frontend/images/icons/external.png new file mode 100644 index 0000000..96de9ae Binary files /dev/null and b/frontend/images/icons/external.png differ diff --git a/frontend/images/icons/fill.svg b/frontend/images/icons/fill.svg new file mode 100644 index 0000000..2e516ab --- /dev/null +++ b/frontend/images/icons/fill.svg @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/frontend/images/icons/gradient.png b/frontend/images/icons/gradient.png new file mode 100644 index 0000000..653f7c8 Binary files /dev/null and b/frontend/images/icons/gradient.png differ diff --git a/frontend/images/icons/grid.png b/frontend/images/icons/grid.png new file mode 100644 index 0000000..8c7ede9 Binary files /dev/null and b/frontend/images/icons/grid.png differ diff --git a/frontend/images/icons/italic.svg b/frontend/images/icons/italic.svg new file mode 100644 index 0000000..3bcedce --- /dev/null +++ b/frontend/images/icons/italic.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/frontend/images/icons/magic_erase.svg b/frontend/images/icons/magic_erase.svg new file mode 100644 index 0000000..1d4f16b --- /dev/null +++ b/frontend/images/icons/magic_erase.svg @@ -0,0 +1,12 @@ + + + + + + + diff --git a/frontend/images/icons/media.svg b/frontend/images/icons/media.svg new file mode 100644 index 0000000..a9cd09a --- /dev/null +++ b/frontend/images/icons/media.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + diff --git a/frontend/images/icons/menu.svg b/frontend/images/icons/menu.svg new file mode 100644 index 0000000..e06501c --- /dev/null +++ b/frontend/images/icons/menu.svg @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/frontend/images/icons/pencil.svg b/frontend/images/icons/pencil.svg new file mode 100644 index 0000000..fb965a9 --- /dev/null +++ b/frontend/images/icons/pencil.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/images/icons/pick_color.svg b/frontend/images/icons/pick_color.svg new file mode 100644 index 0000000..9c68ef3 --- /dev/null +++ b/frontend/images/icons/pick_color.svg @@ -0,0 +1,19 @@ + + + + + + diff --git a/frontend/images/icons/refresh.svg b/frontend/images/icons/refresh.svg new file mode 100644 index 0000000..205fb29 --- /dev/null +++ b/frontend/images/icons/refresh.svg @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/frontend/images/icons/select.svg b/frontend/images/icons/select.svg new file mode 100644 index 0000000..0fb00e1 --- /dev/null +++ b/frontend/images/icons/select.svg @@ -0,0 +1,14 @@ + + + + + + + diff --git a/frontend/images/icons/selection.svg b/frontend/images/icons/selection.svg new file mode 100644 index 0000000..83f92fc --- /dev/null +++ b/frontend/images/icons/selection.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/images/icons/shape.svg b/frontend/images/icons/shape.svg new file mode 100644 index 0000000..a8d06d4 --- /dev/null +++ b/frontend/images/icons/shape.svg @@ -0,0 +1,6 @@ + + shapes + + + + \ No newline at end of file diff --git a/frontend/images/icons/sharpen.svg b/frontend/images/icons/sharpen.svg new file mode 100644 index 0000000..1945d9f --- /dev/null +++ b/frontend/images/icons/sharpen.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/frontend/images/icons/strikethrough.svg b/frontend/images/icons/strikethrough.svg new file mode 100644 index 0000000..70d5806 --- /dev/null +++ b/frontend/images/icons/strikethrough.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/images/icons/text.svg b/frontend/images/icons/text.svg new file mode 100644 index 0000000..ca2d7bf --- /dev/null +++ b/frontend/images/icons/text.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/frontend/images/icons/underline.svg b/frontend/images/icons/underline.svg new file mode 100644 index 0000000..9a2f8fe --- /dev/null +++ b/frontend/images/icons/underline.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/frontend/images/icons/undo.svg b/frontend/images/icons/undo.svg new file mode 100644 index 0000000..97eda4d --- /dev/null +++ b/frontend/images/icons/undo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/images/icons/view.svg b/frontend/images/icons/view.svg new file mode 100644 index 0000000..4fff164 --- /dev/null +++ b/frontend/images/icons/view.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/frontend/images/logo-colors.png b/frontend/images/logo-colors.png new file mode 100644 index 0000000..8970d09 Binary files /dev/null and b/frontend/images/logo-colors.png differ diff --git a/frontend/images/logo.svg b/frontend/images/logo.svg new file mode 100644 index 0000000..f7f6c8c --- /dev/null +++ b/frontend/images/logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/images/manifest/144x144.png b/frontend/images/manifest/144x144.png new file mode 100644 index 0000000..7c1fd4b Binary files /dev/null and b/frontend/images/manifest/144x144.png differ diff --git a/frontend/images/manifest/168x168.png b/frontend/images/manifest/168x168.png new file mode 100644 index 0000000..4c21ca1 Binary files /dev/null and b/frontend/images/manifest/168x168.png differ diff --git a/frontend/images/manifest/192x192.png b/frontend/images/manifest/192x192.png new file mode 100644 index 0000000..6ded28d Binary files /dev/null and b/frontend/images/manifest/192x192.png differ diff --git a/frontend/images/manifest/48x48.png b/frontend/images/manifest/48x48.png new file mode 100644 index 0000000..2cb8b5e Binary files /dev/null and b/frontend/images/manifest/48x48.png differ diff --git a/frontend/images/manifest/72x72.png b/frontend/images/manifest/72x72.png new file mode 100644 index 0000000..6ec2431 Binary files /dev/null and b/frontend/images/manifest/72x72.png differ diff --git a/frontend/images/manifest/96x96.png b/frontend/images/manifest/96x96.png new file mode 100644 index 0000000..7a934d3 Binary files /dev/null and b/frontend/images/manifest/96x96.png differ diff --git a/frontend/images/preview.gif b/frontend/images/preview.gif new file mode 100644 index 0000000..eb6ea20 Binary files /dev/null and b/frontend/images/preview.gif differ diff --git a/frontend/images/preview.jpg b/frontend/images/preview.jpg new file mode 100644 index 0000000..31dc5ee Binary files /dev/null and b/frontend/images/preview.jpg differ diff --git a/frontend/images/test-collection.json b/frontend/images/test-collection.json new file mode 100644 index 0000000..d0ac2b7 --- /dev/null +++ b/frontend/images/test-collection.json @@ -0,0 +1,1809 @@ +{ + "info": { + "width": 1000, + "height": 750, + "about": "Image data with multi-layers. Can be opened using miniPaint - https://github.com/viliusle/miniPaint", + "date": "2021-06-16", + "version": "4.7.1", + "layer_active": 1, + "guides": [ + { "x": null, "y": 400}, + { "x": null, "y": 600}, + { "x": null, "y": 650} + ] + }, + "layers": [ + { + "id": 1, + "parent_id": 0, + "name": "Image", + "type": "image", + "link": {}, + "x": 50, + "y": 100, + "width": 300, + "width_original": 400, + "height": 239, + "height_original": 380, + "visible": true, + "is_vector": false, + "hide_selection_if_active": false, + "opacity": 100, + "order": 1, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": {}, + "status": null, + "color": "#008000", + "filters": [ + { + "id": 592629679, + "name": "shadow", + "params": { + "x": 10, + "y": 10, + "value": "5", + "color": "#000000" + } + } + ], + "render_function": null + }, + { + "id": 2, + "parent_id": 0, + "name": "Text", + "type": "text", + "link": null, + "x": 49, + "y": 25, + "width": 522, + "width_original": null, + "height": 62, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 2, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "size": 45, + "bold": true, + "italic": false, + "stroke": false, + "align": { + "value": "Left", + "values": [ + "Left", + "Center", + "Right" + ] + }, + "family": { + "value": "Arial", + "values": [ + "Arial", + "Courier", + "Impact", + "Helvetica", + "monospace", + "Times New Roman", + "Verdana" + ] + }, + "stroke_size": 1, + "text": "MiniPaint test collection" + }, + "status": null, + "color": "#323e6c", + "filters": [], + "render_function": [ + "text", + "render" + ] + }, + { + "id": 3, + "parent_id": 0, + "name": "Text (hidden)", + "type": "text", + "link": null, + "x": 580, + "y": 34, + "width": 326, + "width_original": null, + "height": 34, + "height_original": null, + "visible": false, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 3, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "size": 30, + "bold": true, + "italic": false, + "stroke": false, + "align": { + "value": "Left", + "values": [ + "Left", + "Center", + "Right" + ] + }, + "family": { + "value": "Arial", + "values": [ + "Arial", + "Courier", + "Impact", + "Helvetica", + "monospace", + "Times New Roman", + "Verdana" + ] + }, + "stroke_size": 1, + "text": "INVISIBLE TEXT" + }, + "status": null, + "color": "#bb0004", + "filters": [], + "render_function": [ + "text", + "render" + ] + }, + { + "id": 4, + "parent_id": 0, + "name": "Text types", + "type": "text", + "link": null, + "x": 650, + "y": 100, + "width": 300, + "width_original": null, + "height": 213, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 4, + "composition": "source-over", + "rotate": 0, + "data": [ + [ + { + "meta": { + "size": 30, + "bold": true, + "fill_color": "#646464" + }, + "text": "Bold " + }, + { + "meta": { + "size": 30, + "italic": true, + "fill_color": "#646464" + }, + "text": "italic " + }, + { + "meta": { + "size": 30, + "underline": true, + "fill_color": "#646464" + }, + "text": "underline" + } + ], + [ + { + "meta": { + "size": 30, + "strikethrough": true, + "fill_color": "#646464" + }, + "text": "strikethrough" + }, + { + "text": " ", + "meta": { + "size": 30, + "strikethrough": false, + "fill_color": "#646464" + } + }, + { + "meta": { + "size": 20, + "strikethrough": false, + "fill_color": "#646464" + }, + "text": "small" + }, + { + "text": " ", + "meta": { + "size": 30, + "strikethrough": false, + "fill_color": "#646464" + } + }, + { + "text": "big", + "meta": { + "size": 60, + "strikethrough": false, + "fill_color": "#646464" + } + }, + { + "text": " red ", + "meta": { + "size": 30, + "strikethrough": false, + "fill_color": "#ff0000" + } + }, + { + "meta": { + "size": 30, + "strikethrough": false, + "fill_color": "#c8c8c8", + "stroke_color": "#0000ff", + "stroke_size": 1 + }, + "text": "stroke" + } + ], + [ + { + "meta": { + "size": 30, + "strikethrough": false, + "fill_color": "#646464", + "stroke_color": "#008800", + "stroke_size": 0 + }, + "text": "Arial " + }, + { + "meta": { + "size": 30, + "strikethrough": false, + "fill_color": "#646464", + "stroke_color": "#008800", + "stroke_size": 0, + "family": "Courier" + }, + "text": "Courier " + }, + { + "meta": { + "size": 30, + "strikethrough": false, + "fill_color": "#646464", + "stroke_color": "#008800", + "stroke_size": 0, + "family": "Special Elite" + }, + "text": "Elite" + } + ], + [ + { + "meta": { + "size": 30, + "strikethrough": false, + "fill_color": "#646464", + "stroke_color": "#008800", + "stroke_size": 0, + "family": "Tahoma", + "kerning": 15 + }, + "text": "Kerning" + } + ] + ], + "params": { + "boundary": "box", + "text_direction": "ltr", + "wrap_direction": "ttb", + "halign": "left", + "valign": "top", + "wrap": "letter" + }, + "status": null, + "color": "#008000", + "filters": [], + "render_function": [ + "text", + "render" + ] + }, + { + "id": 5, + "parent_id": 0, + "name": "Text - center", + "type": "text", + "link": null, + "x": 650, + "y": 309.5, + "width": 300, + "width_original": null, + "height": 41, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 5, + "composition": "source-over", + "rotate": 0, + "data": [ + [ + { + "text": "Center", + "meta": { + "family": "Tahoma", + "size": 25, + "fill_color": "#646464", + "stroke_color": "#008800" + } + } + ] + ], + "params": { + "boundary": "box", + "text_direction": "ltr", + "wrap_direction": "ttb", + "halign": "center", + "valign": "top", + "wrap": "letter" + }, + "status": null, + "color": "#008000", + "filters": [], + "render_function": [ + "text", + "render" + ] + }, + { + "id": 6, + "parent_id": 0, + "name": "Text - right", + "type": "text", + "link": null, + "x": 650, + "y": 350, + "width": 300, + "width_original": null, + "height": 43, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 6, + "composition": "source-over", + "rotate": 0, + "data": [ + [ + { + "text": "Right side", + "meta": { + "family": "Tahoma", + "size": 25, + "fill_color": "#646464", + "stroke_color": "#008800" + } + } + ] + ], + "params": { + "boundary": "box", + "text_direction": "ltr", + "wrap_direction": "ttb", + "halign": "right", + "valign": "top", + "wrap": "letter" + }, + "status": null, + "color": "#008000", + "filters": [], + "render_function": [ + "text", + "render" + ] + }, + { + "id": 7, + "parent_id": 0, + "name": "Text - wrap", + "type": "text", + "link": null, + "x": 400, + "y": 270, + "width": 230, + "width_original": null, + "height": 116, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 7, + "composition": "source-over", + "rotate": 0, + "data": [ + [ + { + "meta": { + "size": 16, + "fill_color": "#323232", + "stroke_color": "#0000ff", + "family": "Verdana", + "leading": 4 + }, + "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque dictum, ipsum sed bibendum posuere, eros erat blandit justo." + } + ] + ], + "params": { + "boundary": "box", + "text_direction": "ltr", + "wrap_direction": "ttb", + "halign": "left", + "valign": "top", + "wrap": "letter" + }, + "status": null, + "color": "#008000", + "filters": [], + "render_function": [ + "text", + "render" + ] + }, + { + "id": 8, + "parent_id": 0, + "name": "Line", + "type": "line", + "link": null, + "x": 50, + "y": 600, + "width": 30, + "width_original": null, + "height": -200, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 8, + "composition": "source-over", + "rotate": null, + "data": null, + "params": { + "size": 5 + }, + "status": null, + "color": "#008000", + "filters": [], + "render_function": [ + "line", + "render" + ] + }, + { + "id": 9, + "parent_id": 0, + "name": "Arrow", + "type": "arrow", + "link": null, + "x": 130, + "y": 400, + "width": -30, + "width_original": null, + "height": 200, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 9, + "composition": "source-over", + "rotate": null, + "data": null, + "params": { + "size": 5 + }, + "status": null, + "color": "#606060", + "filters": [], + "render_function": [ + "arrow", + "render" + ] + }, + { + "id": 10, + "parent_id": 0, + "name": "Rectangle", + "type": "rectangle", + "link": null, + "x": 260, + "y": 500, + "width": 50, + "width_original": null, + "height": 100, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 10, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "fill": false, + "square": false, + "border_size": 4, + "border": true, + "border_color": "#1b1bd8", + "fill_color": "#1b1bd8" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "rectangle", + "render" + ] + }, + { + "id": 11, + "parent_id": 0, + "name": "Ellipse", + "type": "ellipse", + "link": null, + "x": 330, + "y": 400, + "width": 50, + "width_original": null, + "height": 90, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 11, + "composition": "source-over", + "rotate": 0, + "data": { + "center_x": 927, + "center_y": 28 + }, + "params": { + "fill": true, + "circle": false, + "border_size": 1, + "border": false, + "border_color": "#c80000", + "fill_color": "#c80000" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "ellipse", + "render" + ] + }, + { + "id": 12, + "parent_id": 0, + "name": "Gradient (radial)", + "type": "gradient", + "link": null, + "x": 915, + "y": 40, + "width": 20, + "width_original": null, + "height": 20, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 12, + "composition": "source-over", + "rotate": null, + "data": { + "center_x": 486, + "center_y": 504 + }, + "params": { + "color_1": "#008000", + "color_2": "#ffffff", + "alpha": 0, + "radial": true, + "radial_power": 50 + }, + "status": null, + "color": "#40ff40", + "filters": [], + "render_function": [ + "gradient", + "render" + ] + }, + { + "id": 13, + "parent_id": 0, + "name": "Gradient (linear)", + "type": "gradient", + "link": null, + "x": 985, + "y": 56, + "width": -100, + "width_original": null, + "height": 50, + "height_original": null, + "visible": true, + "is_vector": false, + "hide_selection_if_active": false, + "opacity": 100, + "order": 13, + "composition": "source-over", + "rotate": null, + "data": { + "center_x": 1185, + "center_y": 540 + }, + "params": { + "color_1": "#008000", + "color_2": "#ffffff", + "alpha": 0, + "radial": false, + "radial_power": 50 + }, + "status": null, + "color": "#c4f5b2", + "filters": [], + "render_function": [ + "gradient", + "render" + ] + }, + { + "id": 14, + "parent_id": 0, + "name": "Borders", + "type": "borders", + "link": null, + "x": 0, + "y": 0, + "width": 1000, + "width_original": null, + "height": 750, + "height_original": null, + "visible": true, + "is_vector": false, + "hide_selection_if_active": false, + "opacity": 100, + "order": 14, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "size": 15, + "shadow": false + }, + "status": null, + "color": "#02740f", + "filters": [], + "render_function": [ + "borders", + "render" + ] + }, + { + "id": 15, + "parent_id": 0, + "name": "Rectangle (radius)", + "type": "rectangle", + "link": null, + "x": 260, + "y": 400, + "width": 50, + "width_original": null, + "height": 90, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 15, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "radius": 999, + "fill": true, + "square": false, + "border_size": 1, + "border": false, + "border_color": "#6b6b6b", + "fill_color": "#6b6b6b" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "rectangle", + "render" + ] + }, + { + "id": 16, + "parent_id": 0, + "name": "Rectangle", + "type": "rectangle", + "link": null, + "x": 177, + "y": 400, + "width": 30, + "width_original": null, + "height": 150, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 16, + "composition": "source-over", + "rotate": 10, + "data": null, + "params": { + "fill": true, + "square": false, + "border_size": 1, + "border": false, + "border_color": "#55b955", + "fill_color": "#55b955" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "rectangle", + "render" + ] + }, + { + "id": 17, + "parent_id": 0, + "name": "Rectangle (composition)", + "type": "rectangle", + "link": null, + "x": 147, + "y": 424, + "width": 100, + "width_original": null, + "height": 10, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 17, + "composition": "xor", + "rotate": 0, + "data": null, + "params": { + "radius": 0, + "fill": true, + "square": false, + "border_size": 1, + "border": false, + "border_color": "#6b6b6b", + "fill_color": "#6b6b6b" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "rectangle", + "render" + ] + }, + { + "id": 18, + "parent_id": 0, + "name": "Rectangle (opacity)", + "type": "rectangle", + "link": null, + "x": 177, + "y": 445, + "width": 30, + "width_original": null, + "height": 150, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 50, + "order": 18, + "composition": "source-over", + "rotate": -15, + "data": null, + "params": { + "fill": true, + "square": false, + "border_size": 1, + "border": false, + "border_color": "#e01a1a", + "fill_color": "#e01a1a" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "rectangle", + "render" + ] + }, + { + "id": 19, + "parent_id": 0, + "name": "Brush", + "type": "brush", + "link": null, + "x": 410, + "y": 120, + "width": 82, + "width_original": null, + "height": 49, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": true, + "opacity": 100, + "order": 19, + "composition": "source-over", + "rotate": null, + "data": [ + [ + [0,48,4],[0,38,4],[1,28,4],[4,18,4],[7,10,4],[8,6,4],[8,0,4],[10,2,4],[14,10,4],[16,14,4], + [19,19,4],[21,20,4],[24,22,4],[25,22,4],[27,22,4],[31,22,4],[34,20,4],[41,13,4],[45,8,4], + [49,5,4],[52,3,4],[53,2,4],[54,2,4],[55,2,4],[59,5,4],[61,15,4],[62,21,4],[62,25,4],[62,26,4], + [62,26,4] + ], + [ + [8,52,4],[23,49,4],[38,46,4],[53,43,4],[63,39,4],[70,37,4],[74,36,4],[76,36,4] + ] + ], + "params": { + "size": 4, + "pressure": false + }, + "status": null, + "color": "#008000", + "filters": [], + "render_function": [ + "brush", + "render" + ] + }, + { + "id": 20, + "parent_id": 0, + "name": "Brush (pressure)", + "type": "brush", + "link": null, + "x": 530, + "y": 120, + "width": 82, + "width_original": null, + "height": 49, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": true, + "opacity": 100, + "order": 20, + "composition": "source-over", + "rotate": null, + "data": [ + [ + [0,48,4],[0,38,4],[1,28,4],[4,18,4],[7,10,4],[8,6,4],[8,0,4],[10,2,4],[14,10,4],[16,14,4], + [19,19,4],[21,20,4],[24,22,4],[25,22,4],[27,22,4],[31,22,4],[34,20,4],[41,13,4],[45,8,3], + [49,5,3],[52,3,3],[53,2,3],[54,2,2],[55,2,2],[59,5,2],[61,15,2],[62,21,1],[62,25,1],[62,26,1], + [62,26,1] + ], + [ + [8,52,1],[23,49,2],[38,46,3],[53,43,4],[63,39,5],[70,37,4],[74,36,3],[76,36,2] + ] + ], + "params": { + "size": 4, + "pressure": true + }, + "status": null, + "color": "#008000", + "filters": [], + "render_function": [ + "brush", + "render" + ] + }, + { + "id": 21, + "parent_id": 0, + "name": "Pencil (antialiasing)", + "type": "pencil", + "link": null, + "x": 410, + "y": 200, + "width": 53, + "width_original": null, + "height": 43, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": true, + "opacity": 100, + "order": 21, + "composition": "source-over", + "rotate": null, + "data": [ + [0,39],[4,31],[8,19],[11,15],[13,13],[14,16],[16,21],[18,26],[19,28],[21,28],[26,21],[37,7],[42,1], + [43,0],[43,5],[43,9],[44,13],[45,16],[46,19],[46,21],[46,21],null,[12,43],[20,41],[28,39],[37,35], + [42,33],[46,31],[47,31],[49,30],[51,29],[53,28],[53,28] + ], + "params": { + "antialiasing": true, + "size": 2 + }, + "status": null, + "color": "#ff0000", + "filters": [], + "render_function": [ + "pencil", + "render" + ] + }, + { + "id": 22, + "parent_id": 0, + "name": "Pencil", + "type": "pencil", + "link": null, + "x": 530, + "y": 200, + "width": 40, + "width_original": null, + "height": 35, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": true, + "opacity": 100, + "order": 22, + "composition": "source-over", + "rotate": null, + "data": [ + [0,28],[1,21],[5,13],[7,11],[7,10],[9,14],[11,20],[12,23],[12,24],[13,24],[17,22],[25,13],[33,3],[35,0], + [35,4],[35,9],[34,15],[34,17],[34,18],[34,18],null,[5,35],[17,32],[27,29],[31,28],[36,27],[38,26], + [39,26],[40,26],[40,26] + ], + "params": { + "antialiasing": false, + "size": 2 + }, + "status": null, + "color": "#ff0000", + "filters": [], + "render_function": [ + "pencil", + "render" + ] + }, + { + "id": 23, + "parent_id": 0, + "name": "Cylinder", + "type": "cylinder", + "link": null, + "x": 411, + "y": 400, + "width": 48, + "width_original": null, + "height": 70, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 23, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "cylinder", + "render" + ] + }, + { + "id": 24, + "parent_id": 0, + "name": "Heart", + "type": "heart", + "link": null, + "x": 830, + "y": 541, + "width": 70, + "width_original": null, + "height": 58, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 24, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "heart", + "render" + ] + }, + { + "id": 25, + "parent_id": 0, + "name": "Hexagon", + "type": "hexagon", + "link": null, + "x": 480, + "y": 400, + "width": 70, + "width_original": null, + "height": 70, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 25, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "hexagon", + "render" + ] + }, + { + "id": 26, + "parent_id": 0, + "name": "Pentagon", + "type": "pentagon", + "link": null, + "x": 480, + "y": 530, + "width": 70, + "width_original": null, + "height": 70, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 26, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "pentagon", + "render" + ] + }, + { + "id": 27, + "parent_id": 0, + "name": "Parallelogram", + "type": "parallelogram", + "link": null, + "x": 570, + "y": 400, + "width": 70, + "width_original": null, + "height": 50, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 27, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "parallelogram", + "render" + ] + }, + { + "id": 28, + "parent_id": 0, + "name": "Trapezoid", + "type": "trapezoid", + "link": null, + "x": 570, + "y": 558, + "width": 70, + "width_original": null, + "height": 40, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 28, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "trapezoid", + "render" + ] + }, + { + "id": 29, + "parent_id": 0, + "name": "Plus", + "type": "plus", + "link": null, + "x": 650, + "y": 400, + "width": 60, + "width_original": null, + "height": 60, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 29, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "plus", + "render" + ] + }, + { + "id": 30, + "parent_id": 0, + "name": "Right_triangle", + "type": "right_triangle", + "link": null, + "x": 740, + "y": 400, + "width": 70, + "width_original": null, + "height": 70, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 30, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "right_triangle", + "render" + ] + }, + { + "id": 31, + "parent_id": 0, + "name": "Triangle", + "type": "triangle", + "link": null, + "x": 740, + "y": 528, + "width": 81, + "width_original": null, + "height": 70, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 31, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "triangle", + "render" + ] + }, + { + "id": 32, + "parent_id": 0, + "name": "Romb", + "type": "romb", + "link": null, + "x": 410, + "y": 536, + "width": 50, + "width_original": null, + "height": 63, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 32, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "romb", + "render" + ] + }, + { + "id": 34, + "parent_id": 0, + "name": "Star", + "type": "star", + "link": null, + "x": 320, + "y": 529, + "width": 70, + "width_original": null, + "height": 70, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 34, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "star", + "render" + ] + }, + { + "id": 35, + "parent_id": 0, + "name": "Star-24", + "type": "star24", + "link": null, + "x": 830, + "y": 400, + "width": 70, + "width_original": null, + "height": 70, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 35, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "star24", + "render" + ] + }, + { + "id": 39, + "parent_id": 0, + "name": "Plus (borders)", + "type": "plus", + "link": null, + "x": 620, + "y": 466, + "width": 50, + "width_original": null, + "height": 50, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 39, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": false, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "plus", + "render" + ] + }, + { + "id": 40, + "parent_id": 0, + "name": "Plus (filled)", + "type": "plus", + "link": null, + "x": 680, + "y": 466, + "width": 50, + "width_original": null, + "height": 50, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 40, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": false, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "plus", + "render" + ] + }, + { + "id": 41, + "parent_id": 0, + "name": "Plus (rotated)", + "type": "plus", + "link": null, + "x": 660, + "y": 551, + "width": 50, + "width_original": null, + "height": 50, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 41, + "composition": "source-over", + "rotate": 47, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "plus", + "render" + ] + }, + { + "id": 42, + "parent_id": 0, + "name": "Human", + "type": "human", + "link": null, + "x": 925, + "y": 400, + "width": 35, + "width_original": null, + "height": 100, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 42, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "human", + "render" + ] + }, + { + "id": 43, + "parent_id": 0, + "name": "Cog", + "type": "cog", + "link": null, + "x": 50, + "y": 650, + "width": 77, + "width_original": null, + "height": 77, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 43, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "fill_color": "#555555" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "cog", + "render" + ] + }, + { + "id": 44, + "parent_id": 0, + "name": "Tear", + "type": "tear", + "link": null, + "x": 140, + "y": 650, + "width": 61, + "width_original": null, + "height": 76.25, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 44, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "tear", + "render" + ] + }, + { + "id": 45, + "parent_id": 0, + "name": "Moon", + "type": "moon", + "link": null, + "x": 220, + "y": 650, + "width": 58, + "width_original": null, + "height": 72.5, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 45, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "moon", + "render" + ] + }, + { + "id": 46, + "parent_id": 0, + "name": "Callout", + "type": "callout", + "link": null, + "x": 300, + "y": 650, + "width": 81.60000000000001, + "width_original": null, + "height": 69, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": false, + "opacity": 100, + "order": 46, + "composition": "source-over", + "rotate": 0, + "data": null, + "params": { + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": null, + "color": null, + "filters": [], + "render_function": [ + "callout", + "render" + ] + }, + { + "id": 48, + "parent_id": 0, + "name": "Bezier_curve #48", + "type": "bezier_curve", + "link": null, + "x": 0, + "y": 0, + "width": null, + "width_original": null, + "height": null, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": true, + "opacity": 100, + "order": 48, + "composition": "source-over", + "rotate": null, + "data": { + "start": {"x": 400, "y": 660.5}, + "cp1": {"x": 477.5, "y": 661.5}, + "cp2": {"x": 400, "y": 708}, + "end": {"x": 480, "y": 707.5} + }, + "params": { + "size": 4 + }, + "status": null, + "color": "#ff0000", + "filters": [], + "render_function": [ + "bezier_curve", + "render" + ] + }, + { + "id": 49, + "parent_id": 0, + "name": "Polygon #49", + "type": "polygon", + "link": null, + "x": 0, + "y": 0, + "width": null, + "width_original": null, + "height": null, + "height_original": null, + "visible": true, + "is_vector": true, + "hide_selection_if_active": true, + "opacity": 100, + "order": 49, + "composition": "source-over", + "rotate": null, + "data": [ + {"x": 950, "y": 551}, + {"x": 950, "y": 599}, + {"x": 900, "y": 599}, + {"x": 935, "y": 586}, + {"x": 915, "y": 533.2} + ], + "params": { + "size": 4, + "border_size": 4, + "border": true, + "fill": true, + "border_color": "#555555", + "fill_color": "#aaaaaa" + }, + "status": "draft", + "color": "#ff0000", + "filters": [], + "render_function": [ + "polygon", + "render" + ] + } + ], + "data": [ + { + "id": 1, + "data": "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8' standalone='no'%3F%3E%3C!-- Created with Inkscape (http://www.inkscape.org/) --%3E%3Csvg xmlns:svg='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg' version='1.0' width='400' height='380' id='svg2'%3E%3Cdefs id='defs5' /%3E%3Cpath d='M 151.34904,307.20455 L 264.34904,307.20455 C 264.34904,291.14096 263.2021,287.95455 236.59904,287.95455 C 240.84904,275.20455 258.12424,244.35808 267.72404,244.35808 C 276.21707,244.35808 286.34904,244.82592 286.34904,264.20455 C 286.34904,286.20455 323.37171,321.67547 332.34904,307.20455 C 345.72769,285.63897 309.34904,292.21514 309.34904,240.20455 C 309.34904,169.05135 350.87417,179.18071 350.87417,139.20455 C 350.87417,119.20455 345.34904,116.50374 345.34904,102.20455 C 345.34904,83.30695 361.99717,84.403577 358.75805,68.734879 C 356.52061,57.911656 354.76962,49.23199 353.46516,36.143889 C 352.53959,26.857305 352.24452,16.959398 342.59855,17.357382 C 331.26505,17.824992 326.96549,37.77419 309.34904,39.204549 C 291.76851,40.631991 276.77834,24.238028 269.97404,26.579549 C 263.22709,28.901334 265.34904,47.204549 269.34904,60.204549 C 275.63588,80.636771 289.34904,107.20455 264.34904,111.20455 C 239.34904,115.20455 196.34904,119.20455 165.34904,160.20455 C 134.34904,201.20455 135.49342,249.3212 123.34904,264.20455 C 82.590696,314.15529 40.823919,293.64625 40.823919,335.20455 C 40.823919,353.81019 72.349045,367.20455 77.349045,361.20455 C 82.349045,355.20455 34.863764,337.32587 87.995492,316.20455 C 133.38711,298.16014 137.43914,294.47663 151.34904,307.20455 z ' style='fill:%23a70d0d;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1' id='path1891' /%3E%3C/svg%3E%0A" + } + ] +} diff --git a/frontend/index.html b/frontend/index.html index d729151..7c7a022 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,12 +1,104 @@ - - - - - AI Photo Edit - - -
- - + + + + + miniPaint - image editor + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + + + +
+
+
+
+ +
+ Your browser does not support canvas or JavaScript is not enabled. +
+
+
+
+
+ + +
+
+ + +
+ +
+ diff --git a/frontend/manifest-disabled.json b/frontend/manifest-disabled.json new file mode 100644 index 0000000..51f4934 --- /dev/null +++ b/frontend/manifest-disabled.json @@ -0,0 +1,41 @@ +{ + "name": "miniPaint", + "short_name": "miniPaint", + "start_url": "/", + "display": "standalone", + "orientation": "landscape", + "background_color": "#666d6f", + "description": "miniPaint is free online image editor using HTML5.", + "icons": [ + { + "src": "images/manifest/48x48.png", + "sizes": "48x48", + "type": "image/png" + }, + { + "src": "images/manifest/72x72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "images/manifest/96x96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "images/manifest/144x144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "images/manifest/168x168.png", + "sizes": "168x168", + "type": "image/png" + }, + { + "src": "images/manifest/192x192.png", + "sizes": "192x192", + "type": "image/png" + } + ] +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..55c7088 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,10750 @@ +{ + "name": "miniPaint", + "version": "4.13.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "miniPaint", + "version": "4.13.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.14.5", + "alertifyjs": "^1.13.1", + "blueimp-canvas-to-blob": "^3.28.0", + "exif-js": "^2.3.0", + "file-saver": "^2.0.5", + "fuzzysort": "^1.1.4", + "gif.js.optimized": "^1.0.1", + "hermite-resize": "git+https://github.com/viliusle/Hermite-resize.git", + "jquery": "^3.5.1", + "pica": "^7.0.0", + "semver-compare": "^1.0.0", + "uuid": "^8.3.2", + "webfontloader": "^1.6.28" + }, + "devDependencies": { + "@babel/core": "^7.14.5", + "@babel/plugin-transform-runtime": "^7.14.5", + "@babel/preset-env": "^7.14.5", + "babel-loader": "^8.2.2", + "css-loader": "^5.2.6", + "source-map-loader": "^3.0.0", + "style-loader": "^2.0.0", + "webpack": "^5.76.0", + "webpack-cli": "^4.7.2", + "webpack-dev-server": "^4.3.1" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz", + "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.5", + "@babel/parser": "^7.23.5", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", + "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.5.tgz", + "integrity": "sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", + "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz", + "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", + "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", + "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz", + "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", + "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz", + "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", + "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.4.tgz", + "integrity": "sha512-ITwqpb6V4btwUG0YJR82o2QvmWrLgDnx/p2A3CTPYGaRgULkDiC0DRA2C4jlRB9uXGUEfaSS/IGHfVW+ohzYDw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.5.tgz", + "integrity": "sha512-0d/uxVD6tFGWXGDSfyMD1p2otoaKmu6+GD+NfAx0tMaH+dxORnp7T9TaVQ6mKyya7iBtCIVxHjWT7MuzzM9z+A==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.4", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.5", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.3", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.3", + "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", + "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz", + "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.5", + "@babel/types": "^7.23.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", + "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.44.8", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", + "integrity": "sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.1.tgz", + "integrity": "sha512-T2qwhjWwGH81vUEx4EXmBKsTJRXFXNZTL4v0gi01+zyBmCwzE6TyHszqX01m+QHTEq+EZNo13NeJIdEqf+Myrg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.10.tgz", + "integrity": "sha512-y6PJDYN4xYBxwd22l+OVH35N+1fCYWiuC3aiP2SlXVE6Lo7SS+rSx9r89hLxrP4pn6n1lBGhHJ12pj3F3Mpttw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.10", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", + "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "dev": true, + "dependencies": { + "envinfo": "^7.7.3" + }, + "peerDependencies": { + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "dev": true, + "peerDependencies": { + "webpack-cli": "4.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/alertifyjs": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/alertifyjs/-/alertifyjs-1.13.1.tgz", + "integrity": "sha512-CckZE2dZDsEEXglOXKxT00vUDV5A6udZom+bn1XHdIWlbSFZgYq7UXCBlwkShhIH3Li/1VxLmr55GOQFQ12WSg==" + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "node_modules/babel-loader": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", + "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", + "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", + "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.3", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz", + "integrity": "sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.3", + "core-js-compat": "^3.33.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", + "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.3" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/blueimp-canvas-to-blob": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz", + "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==" + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/bonjour-service": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "dev": true, + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001565", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001565.tgz", + "integrity": "sha512-xrE//a3O7TP0vaJ8ikzkD2c2NgcVUvsEe2IvFTntV4Yd1Z9FVzh+gW+enX96L0psrbaFMcVcH2l90xNuGDWc8w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/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==", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/core-js-compat": { + "version": "3.33.3", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.3.tgz", + "integrity": "sha512-cNzGqFsh3Ot+529GIXacjTJ7kegdt5fPXxCBVS1G0iaZpuo/tBz399ymceLJveQhFFZ8qThHiP3fzuoQjKN2ow==", + "dev": true, + "dependencies": { + "browserslist": "^4.22.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", + "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.15", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^3.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.27.0 || ^5.0.0" + } + }, + "node_modules/css-loader/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-loader/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.600", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.600.tgz", + "integrity": "sha512-KD6CWjf1BnQG+NsXuyiTDDT1eV13sKuYsOUioXkQweYTQIbgHkXPry9K7M+7cKtYHnSUPitVaLrXYB1jTkkYrw==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exif-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/exif-js/-/exif-js-2.3.0.tgz", + "integrity": "sha512-1Og9pAzG2FZRVlaavH8bB8BTeHcjMdJhKmeQITkX+uLRCD0xPtKAdZ2clZmQdJ56p9adXtJ8+jwrGp/4505lYg==" + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, + "node_modules/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==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fuzzysort": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-1.9.0.tgz", + "integrity": "sha512-MOxCT0qLTwLqmEwc7UtU045RKef7mc8Qz8eR4r2bLNEq9dy/c3ZKMEFp6IEst69otkQdFZ4FfgH2dmZD+ddX1g==" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gif.js.optimized": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gif.js.optimized/-/gif.js.optimized-1.0.1.tgz", + "integrity": "sha512-IS0F42Xken6lp/iR4irgG4r52tvxRkEKsXGZmlUHUOb00SWNMezJOJwkVaJk2MLW53rqzMbPnnBtEhs9hcMJ9w==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/glur": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/glur/-/glur-1.1.2.tgz", + "integrity": "sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA==" + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermite-resize": { + "version": "2.2.10", + "resolved": "git+ssh://git@github.com/viliusle/Hermite-resize.git#fae53290d2b03520a6fc81d734c3028902a599c0", + "license": "MIT" + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", + "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ipaddr.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/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==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/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==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/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==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/multimath": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/multimath/-/multimath-2.0.0.tgz", + "integrity": "sha512-toRx66cAMJ+Ccz7pMIg38xSIrtnbozk0dchXezwQDMgQmbGpfxjtv68H+L00iFL8hxDaVjrmwAFSb3I6bg8Q2g==", + "dependencies": { + "glur": "^1.1.2", + "object-assign": "^4.1.1" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/pica": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/pica/-/pica-7.1.1.tgz", + "integrity": "sha512-WY73tMvNzXWEld2LicT9Y260L43isrZ85tPuqRyvtkljSDLmnNFQmZICt4xUJMVulmcc6L9O7jbBrtx3DOz/YQ==", + "dependencies": { + "glur": "^1.1.2", + "inherits": "^2.0.3", + "multimath": "^2.0.0", + "object-assign": "^4.1.1", + "webworkify": "^1.5.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", + "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", + "dev": true, + "dependencies": { + "abab": "^2.0.5", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/style-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", + "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/style-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", + "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/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==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webfontloader": { + "version": "1.6.28", + "resolved": "https://registry.npmjs.org/webfontloader/-/webfontloader-1.6.28.tgz", + "integrity": "sha512-Egb0oFEga6f+nSgasH3E0M405Pzn6y3/9tOVanv/DLfa1YBIgcv90L18YyWnvXkRbIM17v5Kv6IT2N6g1x5tvQ==" + }, + "node_modules/webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "cross-spawn": "^7.0.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "@webpack-cli/migrate": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", + "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-server/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/webworkify": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/webworkify/-/webworkify-1.5.0.tgz", + "integrity": "sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g==" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + } + }, + "@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true + }, + "@babel/core": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz", + "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.5", + "@babel/parser": "^7.23.5", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "@babel/generator": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", + "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", + "dev": true, + "requires": { + "@babel/types": "^7.23.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.5.tgz", + "integrity": "sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", + "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "requires": { + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + } + }, + "@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + } + }, + "@babel/helpers": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz", + "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5" + } + }, + "@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", + "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==", + "dev": true + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.23.3" + } + }, + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", + "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "requires": {} + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-attributes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-async-generator-functions": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz", + "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-static-block": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", + "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.15" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dynamic-import": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-export-namespace-from": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz", + "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-json-strings": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-logical-assignment-operators": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", + "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-transform-numeric-separator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-transform-object-rest-spread": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.23.3" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20" + } + }, + "@babel/plugin-transform-optional-catch-binding": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-transform-optional-chaining": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-methods": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-property-in-object": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.4.tgz", + "integrity": "sha512-ITwqpb6V4btwUG0YJR82o2QvmWrLgDnx/p2A3CTPYGaRgULkDiC0DRA2C4jlRB9uXGUEfaSS/IGHfVW+ohzYDw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "semver": "^6.3.1" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-property-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-sets-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/preset-env": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.5.tgz", + "integrity": "sha512-0d/uxVD6tFGWXGDSfyMD1p2otoaKmu6+GD+NfAx0tMaH+dxORnp7T9TaVQ6mKyya7iBtCIVxHjWT7MuzzM9z+A==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.4", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.5", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.3", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.3", + "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + } + }, + "@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "@babel/runtime": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", + "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, + "@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + } + }, + "@babel/traverse": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz", + "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.5", + "@babel/types": "^7.23.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", + "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + }, + "@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "@types/eslint": { + "version": "8.44.8", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", + "integrity": "sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "@types/http-proxy": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "@types/node": { + "version": "20.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.1.tgz", + "integrity": "sha512-T2qwhjWwGH81vUEx4EXmBKsTJRXFXNZTL4v0gi01+zyBmCwzE6TyHszqX01m+QHTEq+EZNo13NeJIdEqf+Myrg==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, + "@types/node-forge": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.10.tgz", + "integrity": "sha512-y6PJDYN4xYBxwd22l+OVH35N+1fCYWiuC3aiP2SlXVE6Lo7SS+rSx9r89hLxrP4pn6n1lBGhHJ12pj3F3Mpttw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/qs": { + "version": "6.9.10", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", + "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, + "requires": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "requires": {} + }, + "@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "dev": true, + "requires": { + "envinfo": "^7.7.3" + } + }, + "@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "dev": true, + "requires": {} + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true + }, + "acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "alertifyjs": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/alertifyjs/-/alertifyjs-1.13.1.tgz", + "integrity": "sha512-CckZE2dZDsEEXglOXKxT00vUDV5A6udZom+bn1XHdIWlbSFZgYq7UXCBlwkShhIH3Li/1VxLmr55GOQFQ12WSg==" + }, + "ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "babel-loader": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", + "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", + "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.3", + "semver": "^6.3.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz", + "integrity": "sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.4.3", + "core-js-compat": "^3.33.1" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", + "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.4.3" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "blueimp-canvas-to-blob": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz", + "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==" + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "bonjour-service": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "dev": true, + "requires": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true + }, + "call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + } + }, + "caniuse-lite": { + "version": "1.0.30001565", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001565.tgz", + "integrity": "sha512-xrE//a3O7TP0vaJ8ikzkD2c2NgcVUvsEe2IvFTntV4Yd1Z9FVzh+gW+enX96L0psrbaFMcVcH2l90xNuGDWc8w==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "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==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "core-js-compat": { + "version": "3.33.3", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.3.tgz", + "integrity": "sha512-cNzGqFsh3Ot+529GIXacjTJ7kegdt5fPXxCBVS1G0iaZpuo/tBz399ymceLJveQhFFZ8qThHiP3fzuoQjKN2ow==", + "dev": true, + "requires": { + "browserslist": "^4.22.1" + } + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "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" + } + }, + "css-loader": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", + "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.15", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^3.0.0", + "semver": "^7.3.5" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "requires": { + "execa": "^5.0.0" + } + }, + "define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + } + }, + "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==", + "dev": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "requires": { + "@leichtgewicht/ip-codec": "^2.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.600", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.600.tgz", + "integrity": "sha512-KD6CWjf1BnQG+NsXuyiTDDT1eV13sKuYsOUioXkQweYTQIbgHkXPry9K7M+7cKtYHnSUPitVaLrXYB1jTkkYrw==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true + }, + "enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "envinfo": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "dev": true + }, + "es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exif-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/exif-js/-/exif-js-2.3.0.tgz", + "integrity": "sha512-1Og9pAzG2FZRVlaavH8bB8BTeHcjMdJhKmeQITkX+uLRCD0xPtKAdZ2clZmQdJ56p9adXtJ8+jwrGp/4505lYg==" + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, + "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==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "dev": true + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, + "fs-monkey": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "fuzzysort": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-1.9.0.tgz", + "integrity": "sha512-MOxCT0qLTwLqmEwc7UtU045RKef7mc8Qz8eR4r2bLNEq9dy/c3ZKMEFp6IEst69otkQdFZ4FfgH2dmZD+ddX1g==" + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "requires": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "gif.js.optimized": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gif.js.optimized/-/gif.js.optimized-1.0.1.tgz", + "integrity": "sha512-IS0F42Xken6lp/iR4irgG4r52tvxRkEKsXGZmlUHUOb00SWNMezJOJwkVaJk2MLW53rqzMbPnnBtEhs9hcMJ9w==" + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "glur": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/glur/-/glur-1.1.2.tgz", + "integrity": "sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA==" + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.2" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, + "hermite-resize": { + "version": "git+ssh://git@github.com/viliusle/Hermite-resize.git#fae53290d2b03520a6fc81d734c3028902a599c0", + "from": "hermite-resize@git+https://github.com/viliusle/Hermite-resize.git" + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "html-entities": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", + "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "dev": true + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "requires": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "requires": {} + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true + }, + "ipaddr.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "requires": { + "hasown": "^2.0.0" + } + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "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==", + "dev": true + }, + "is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true + }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "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==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "launch-editor": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "dev": true, + "requires": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true + }, + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "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==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true + }, + "memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "requires": { + "fs-monkey": "^1.0.4" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "requires": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + } + }, + "multimath": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/multimath/-/multimath-2.0.0.tgz", + "integrity": "sha512-toRx66cAMJ+Ccz7pMIg38xSIrtnbozk0dchXezwQDMgQmbGpfxjtv68H+L00iFL8hxDaVjrmwAFSb3I6bg8Q2g==", + "requires": { + "glur": "^1.1.2", + "object-assign": "^4.1.1" + } + }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true + }, + "node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "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==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "requires": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + } + }, + "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==", + "dev": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "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==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "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.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "pica": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/pica/-/pica-7.1.1.tgz", + "integrity": "sha512-WY73tMvNzXWEld2LicT9Y260L43isrZ85tPuqRyvtkljSDLmnNFQmZICt4xUJMVulmcc6L9O7jbBrtx3DOz/YQ==", + "requires": { + "glur": "^1.1.2", + "inherits": "^2.0.3", + "multimath": "^2.0.0", + "object-assign": "^4.1.1", + "webworkify": "^1.5.0" + } + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "requires": {} + }, + "postcss-modules-local-by-default": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "dependencies": { + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + } + } + }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "requires": { + "resolve": "^1.9.0" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, + "regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "requires": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + } + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + } + } + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "requires": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==" + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "requires": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "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 + }, + "shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "requires": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", + "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", + "dev": true, + "requires": { + "abab": "^2.0.5", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.1" + } + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "style-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", + "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "terser": { + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", + "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + } + }, + "terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "dependencies": { + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "webfontloader": { + "version": "1.6.28", + "resolved": "https://registry.npmjs.org/webfontloader/-/webfontloader-1.6.28.tgz", + "integrity": "sha512-Egb0oFEga6f+nSgasH3E0M405Pzn6y3/9tOVanv/DLfa1YBIgcv90L18YyWnvXkRbIM17v5Kv6IT2N6g1x5tvQ==" + }, + "webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "dependencies": { + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "webpack-cli": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "cross-spawn": "^7.0.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + } + } + }, + "webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "requires": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + } + } + }, + "webpack-dev-server": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", + "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "dev": true, + "requires": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.13.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + } + } + }, + "webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true + }, + "webworkify": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/webworkify/-/webworkify-1.5.0.tgz", + "integrity": "sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g==" + }, + "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" + } + }, + "wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "dev": true, + "requires": {} + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } +} diff --git a/frontend/package.json b/frontend/package.json index ea5b4a1..5282082 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,23 +1,51 @@ { - "name": "ai-photo-edit-frontend", - "version": "1.0.0", - "type": "module", + "name": "miniPaint", + "version": "4.14.2", + "author": "Vilius L.", + "description": "Online graphics editing tool lets create, edit images using HTML5 technologies.", + "keywords": [ + "canvas", + "drawing", + "paint", + "layers", + "effects" + ], "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "lint": "eslint ." + "server": "webpack serve --mode development --env development --open", + "dev": "webpack --mode development", + "build": "webpack --mode production" + }, + "repository": { + "type": "git", + "url": "https://github.com/viliusle/miniPaint" + }, + "homepage": "https://github.com/viliusle/miniPaint", + "license": "MIT", + "devDependencies": { + "@babel/core": "^7.14.5", + "@babel/plugin-transform-runtime": "^7.14.5", + "@babel/preset-env": "^7.14.5", + "babel-loader": "^8.2.2", + "css-loader": "^5.2.6", + "source-map-loader": "^3.0.0", + "style-loader": "^2.0.0", + "webpack": "^5.76.0", + "webpack-cli": "^4.7.2", + "webpack-dev-server": "^4.3.1" }, "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0", - "fabric": "^5.3.0", - "axios": "^1.6.5" - }, - "devDependencies": { - "@types/react": "^18.2.48", - "@types/react-dom": "^18.2.18", - "@vitejs/plugin-react": "^4.2.1", - "vite": "^5.0.12" + "@babel/runtime": "^7.14.5", + "alertifyjs": "^1.13.1", + "blueimp-canvas-to-blob": "^3.28.0", + "exif-js": "^2.3.0", + "file-saver": "^2.0.5", + "fuzzysort": "^1.1.4", + "gif.js.optimized": "^1.0.1", + "hermite-resize": "git+https://github.com/viliusle/Hermite-resize.git", + "jquery": "^3.5.1", + "pica": "^7.0.0", + "semver-compare": "^1.0.0", + "uuid": "^8.3.2", + "webfontloader": "^1.6.28" } } diff --git a/frontend/service-worker.js b/frontend/service-worker.js new file mode 100644 index 0000000..74b64dc --- /dev/null +++ b/frontend/service-worker.js @@ -0,0 +1,68 @@ + +//IMPORTANT - this file is not used !!! + + +// use a cacheName for cache versioning +var cacheName = 'v1:static'; + +// during the install phase you usually want to cache static assets +self.addEventListener('install', function(e) { + // once the SW is installed, go ahead and fetch the resources to make this work offline + e.waitUntil( + caches.open(cacheName).then(function(cache) { + return cache.addAll([ + './', + './dist/bundle.js', + './images/favicon.png', + './images/logo.svg', + './images/logo-colors.png', + './images/icons/animation.svg', + './images/icons/blur.svg', + './images/icons/bold.svg', + './images/icons/brush.svg', + './images/icons/bulge_pinch.svg', + './images/icons/clone.svg', + './images/icons/crop.svg', + './images/icons/delete.svg', + './images/icons/desaturate.svg', + './images/icons/erase.svg', + './images/icons/external.png', + './images/icons/fill.svg', + './images/icons/gradient.png', + './images/icons/grid.png', + './images/icons/italic.svg', + './images/icons/magic_erase.svg', + './images/icons/media.svg', + './images/icons/menu.svg', + './images/icons/pencil.svg', + './images/icons/pick_color.svg', + './images/icons/refresh.svg', + './images/icons/select.svg', + './images/icons/selection.svg', + './images/icons/shape.svg', + './images/icons/sharpen.svg', + './images/icons/strikethrough.svg', + './images/icons/text.svg', + './images/icons/underline.svg', + './images/icons/view.svg' + ]).then(function() { + self.skipWaiting(); + }); + }) + ); +}); + +// when the browser fetches a url +self.addEventListener('fetch', function(event) { + // either respond with the cached object or go ahead and fetch the actual url + event.respondWith( + caches.match(event.request).then(function(response) { + if (response) { + // retrieve from cache + return response; + } + // fetch as normal + return fetch(event.request); + }) + ); +}); diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index a0b9eeb..0000000 --- a/frontend/src/App.css +++ /dev/null @@ -1,652 +0,0 @@ -/* GIMP-like layout */ -* { - box-sizing: border-box; -} - -html, body, #root { - margin: 0; - padding: 0; - height: 100%; - overflow: hidden; -} - -.app { - height: 100vh; - display: flex; - flex-direction: column; - background-color: #1a1a1a; - color: #e0e0e0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; -} - -/* Header / Menu bar */ -.menu-bar { - display: flex; - align-items: center; - justify-content: space-between; - background-color: #2d2d2d; - border-bottom: 1px solid #404040; - padding: 4px 12px; - height: 36px; - flex-shrink: 0; -} - -.menu-bar h1 { - font-size: 14px; - font-weight: 600; - margin: 0; - color: #fff; -} - -.menu-actions { - display: flex; - gap: 8px; -} - -.menu-btn { - background: #404040; - border: none; - color: #e0e0e0; - padding: 4px 12px; - font-size: 12px; - border-radius: 3px; - cursor: pointer; -} - -.menu-btn:hover { - background: #505050; -} - -.menu-btn.primary { - background: #0066cc; -} - -.menu-btn.primary:hover { - background: #0077ee; -} - -/* Main workspace */ -.workspace { - display: flex; - flex: 1; - min-height: 0; - overflow: hidden; -} - -/* Left toolbar - GIMP style vertical tools */ -.toolbar { - width: 48px; - background-color: #2d2d2d; - border-right: 1px solid #404040; - display: flex; - flex-direction: column; - padding: 4px; - gap: 2px; - flex-shrink: 0; -} - -.tool-btn { - width: 40px; - height: 40px; - background: transparent; - border: 2px solid transparent; - border-radius: 4px; - color: #b0b0b0; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; - transition: all 0.15s; -} - -.tool-btn:hover { - background: #404040; - color: #fff; -} - -.tool-btn.active { - background: #0066cc; - border-color: #0088ff; - color: #fff; -} - -.tool-btn[title]:hover::after { - content: attr(title); - position: absolute; - left: 52px; - background: #000; - color: #fff; - padding: 4px 8px; - border-radius: 3px; - font-size: 11px; - white-space: nowrap; - z-index: 100; -} - -.tool-divider { - height: 1px; - background: #404040; - margin: 4px 0; -} - -/* Canvas area - main editing space */ -.canvas-area { - flex: 1; - display: flex; - flex-direction: column; - min-width: 0; - background: #1a1a1a; - position: relative; -} - -.canvas-wrapper { - flex: 1; - position: relative; - overflow: hidden; - background: - linear-gradient(45deg, #252525 25%, transparent 25%), - linear-gradient(-45deg, #252525 25%, transparent 25%), - linear-gradient(45deg, transparent 75%, #252525 75%), - linear-gradient(-45deg, transparent 75%, #252525 75%); - background-size: 20px 20px; - background-position: 0 0, 0 10px, 10px -10px, -10px 0px; - background-color: #1e1e1e; -} - -/* Canvas status bar */ -.canvas-status { - height: 24px; - background: #2d2d2d; - border-top: 1px solid #404040; - display: flex; - align-items: center; - padding: 0 12px; - font-size: 11px; - color: #888; - gap: 20px; -} - -.zoom-controls { - display: flex; - align-items: center; - gap: 8px; -} - -.zoom-btn { - background: #404040; - border: none; - color: #e0e0e0; - width: 24px; - height: 20px; - border-radius: 3px; - cursor: pointer; - font-size: 14px; -} - -.zoom-btn:hover { - background: #505050; -} - -.zoom-level { - min-width: 50px; - text-align: center; -} - -/* Right sidebar */ -.sidebar { - width: 280px; - background-color: #2d2d2d; - border-left: 1px solid #404040; - display: flex; - flex-direction: column; - overflow-y: auto; - flex-shrink: 0; -} - -.sidebar-panel { - border-bottom: 1px solid #404040; -} - -.panel-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 12px; - background: #353535; - cursor: pointer; - user-select: none; -} - -.panel-header h3 { - font-size: 12px; - font-weight: 600; - margin: 0; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.panel-toggle { - font-size: 10px; - color: #888; -} - -.panel-content { - padding: 12px; -} - -.panel-content.collapsed { - display: none; -} - -/* Form controls */ -.control-group { - margin-bottom: 12px; -} - -.control-group:last-child { - margin-bottom: 0; -} - -.control-label { - display: block; - font-size: 11px; - color: #888; - margin-bottom: 4px; - text-transform: uppercase; -} - -.control-row { - display: flex; - gap: 4px; -} - -.mode-btn { - flex: 1; - padding: 6px 8px; - background: #404040; - border: 1px solid #505050; - color: #e0e0e0; - font-size: 11px; - border-radius: 3px; - cursor: pointer; -} - -.mode-btn:hover { - background: #505050; -} - -.mode-btn.active { - background: #0066cc; - border-color: #0088ff; -} - -input[type="range"] { - width: 100%; - height: 4px; - background: #404040; - border-radius: 2px; - outline: none; - -webkit-appearance: none; -} - -input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - width: 14px; - height: 14px; - background: #0088ff; - border-radius: 50%; - cursor: pointer; -} - -textarea { - width: 100%; - background: #1a1a1a; - border: 1px solid #404040; - border-radius: 3px; - color: #e0e0e0; - padding: 8px; - font-size: 12px; - resize: vertical; - min-height: 60px; -} - -textarea:focus { - border-color: #0088ff; - outline: none; -} - -.action-btn { - width: 100%; - padding: 10px; - background: #0066cc; - border: none; - color: white; - font-size: 12px; - font-weight: 600; - border-radius: 3px; - cursor: pointer; - text-transform: uppercase; -} - -.action-btn:hover:not(:disabled) { - background: #0077ee; -} - -.action-btn:disabled { - background: #404040; - color: #666; - cursor: not-allowed; -} - -.action-btn.secondary { - background: #404040; -} - -.action-btn.secondary:hover:not(:disabled) { - background: #505050; -} - -.action-btn.danger { - background: #cc3333; -} - -.action-btn.danger:hover:not(:disabled) { - background: #dd4444; -} - -/* Error banner */ -.error-banner { - background-color: #cc3333; - color: white; - padding: 8px 12px; - font-size: 12px; - display: flex; - justify-content: space-between; - align-items: center; -} - -.error-banner button { - background: none; - border: none; - color: white; - font-size: 16px; - cursor: pointer; - padding: 0; -} - -/* Project setup modal */ -.project-setup-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.8); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; -} - -.project-setup { - background: #2d2d2d; - border: 1px solid #404040; - border-radius: 8px; - padding: 24px; - width: 400px; - max-width: 90vw; -} - -.project-setup h2 { - font-size: 18px; - margin: 0 0 20px 0; - text-align: center; -} - -.setup-form { - display: flex; - flex-direction: column; - gap: 16px; -} - -.form-group { - display: flex; - flex-direction: column; - gap: 6px; -} - -.form-group label { - font-size: 12px; - color: #888; - text-transform: uppercase; -} - -.form-group input[type="text"], -.form-group input[type="file"] { - background: #1a1a1a; - border: 1px solid #404040; - border-radius: 3px; - color: #e0e0e0; - padding: 10px; - font-size: 14px; -} - -.form-group input[type="text"]:focus { - border-color: #0088ff; - outline: none; -} - -.file-name { - font-size: 11px; - color: #0088ff; -} - -.create-project-btn { - background: #0066cc; - border: none; - color: white; - padding: 12px; - font-size: 14px; - font-weight: 600; - border-radius: 3px; - cursor: pointer; - margin-top: 8px; -} - -.create-project-btn:hover:not(:disabled) { - background: #0077ee; -} - -.create-project-btn:disabled { - background: #404040; - color: #666; -} - -/* Layer list */ -.layer-list { - display: flex; - flex-direction: column; - gap: 4px; -} - -.layer-item { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 8px; - background: #353535; - border-radius: 3px; - cursor: pointer; -} - -.layer-item:hover { - background: #404040; -} - -.layer-item.active { - background: #0066cc; -} - -.layer-visibility { - width: 16px; - height: 16px; - background: none; - border: none; - color: #888; - cursor: pointer; - padding: 0; - font-size: 12px; -} - -.layer-visibility.visible { - color: #0088ff; -} - -.layer-name { - flex: 1; - font-size: 12px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* History list */ -.history-list { - display: flex; - flex-direction: column; - gap: 4px; - max-height: 200px; - overflow-y: auto; -} - -.history-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 6px 8px; - background: #353535; - border-radius: 3px; - font-size: 11px; -} - -.history-item:hover { - background: #404040; -} - -.history-item.current { - background: #0066cc; -} - -.history-item-actions { - display: flex; - gap: 4px; -} - -.history-btn { - background: #505050; - border: none; - color: #e0e0e0; - padding: 2px 8px; - font-size: 10px; - border-radius: 2px; - cursor: pointer; -} - -.history-btn:hover { - background: #606060; -} - -/* Eye catalog */ -.eye-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 8px; -} - -.eye-item { - aspect-ratio: 1; - background: #353535; - border: 2px solid transparent; - border-radius: 4px; - cursor: pointer; - overflow: hidden; -} - -.eye-item:hover { - border-color: #0088ff; -} - -.eye-item.selected { - border-color: #0088ff; -} - -.eye-item img { - width: 100%; - height: 100%; - object-fit: cover; -} - -/* Processing overlay */ -.processing-overlay { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.7); - display: flex; - align-items: center; - justify-content: center; - z-index: 100; -} - -.processing-spinner { - width: 40px; - height: 40px; - border: 3px solid #404040; - border-top-color: #0088ff; - border-radius: 50%; - animation: spin 1s linear infinite; -} - -@keyframes spin { - to { transform: rotate(360deg); } -} - -/* Slider with value */ -.slider-row { - display: flex; - align-items: center; - gap: 8px; -} - -.slider-row input[type="range"] { - flex: 1; -} - -.slider-value { - font-size: 11px; - color: #0088ff; - min-width: 35px; - text-align: right; -} - -/* Scrollbar styling */ -::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -::-webkit-scrollbar-track { - background: #1a1a1a; -} - -::-webkit-scrollbar-thumb { - background: #404040; - border-radius: 4px; -} - -::-webkit-scrollbar-thumb:hover { - background: #505050; -} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx deleted file mode 100644 index 8dcfe65..0000000 --- a/frontend/src/App.jsx +++ /dev/null @@ -1,698 +0,0 @@ -import React, { useState, useEffect, useCallback, useRef } from 'react'; -import ImageCanvas from './components/ImageCanvas'; -import { projectsApi, editsApi, toolsApi } from './utils/api'; -import './App.css'; - -// Tool definitions -const TOOLS = { - move: { icon: '✥', name: 'Move', shortcut: 'V' }, - select: { icon: '▢', name: 'Rectangle Select', shortcut: 'R' }, - ellipse: { icon: '○', name: 'Ellipse Select', shortcut: 'E' }, - lasso: { icon: '✎', name: 'Free Select (Lasso)', shortcut: 'F' }, - magic: { icon: '✨', name: 'Smart Select (SAM)', shortcut: 'W' }, - colorPick: { icon: '◉', name: 'Color Select', shortcut: 'U' }, - brush: { icon: '🖌', name: 'Brush', shortcut: 'B' }, - bucket: { icon: '◧', name: 'Bucket Fill', shortcut: 'G' }, - eraser: { icon: '◫', name: 'Eraser', shortcut: 'Shift+E' }, - eyedropper: { icon: '💧', name: 'Color Picker', shortcut: 'O' }, - zoom: { icon: '🔍', name: 'Zoom', shortcut: 'Z' }, - pan: { icon: '✋', name: 'Pan', shortcut: 'H' }, -}; - -function App() { - // Project state - const [project, setProject] = useState(null); - const [imageFile, setImageFile] = useState(null); - const [currentImageUrl, setCurrentImageUrl] = useState(null); - const [showProjectSetup, setShowProjectSetup] = useState(true); - const [projectName, setProjectName] = useState(''); - - // Tool state - const [activeTool, setActiveTool] = useState('select'); - const [selection, setSelection] = useState(null); - - // Edit state - const [prompt, setPrompt] = useState(''); - const [feather, setFeather] = useState(5); - const [mode, setMode] = useState('A'); - const [edits, setEdits] = useState([]); - const [currentEditIndex, setCurrentEditIndex] = useState(-1); - const editsRef = useRef([]); - - // Layer state - const [layers, setLayers] = useState([]); - - // UI state - const [isProcessing, setIsProcessing] = useState(false); - const [error, setError] = useState(null); - const [zoom, setZoom] = useState(100); - const [collapsedPanels, setCollapsedPanels] = useState({}); - const [eyes, setEyes] = useState([]); - const [selectedEye, setSelectedEye] = useState(null); - - // Canvas ref for tool interactions - const canvasRef = useRef(null); - - // Map tool to selection mode - const getSelectionMode = () => { - switch (activeTool) { - case 'select': return 'rectangle'; - case 'ellipse': return 'ellipse'; - case 'lasso': return 'lasso'; - case 'magic': return 'smart'; - case 'colorPick': return 'color'; - default: return null; - } - }; - - // Load eyes catalog - useEffect(() => { - const loadEyes = async () => { - try { - const patches = await fetch('/patches/?category=eyes').then(r => r.json()).catch(() => []); - setEyes(patches); - } catch (err) { - console.error('Failed to load eyes:', err); - } - }; - if (project) loadEyes(); - }, [project]); - - // Create project - const handleCreateProject = async () => { - if (!imageFile) { - setError('Please select an image'); - return; - } - - try { - setError(null); - setIsProcessing(true); - - const defaultName = projectName.trim() || - imageFile.name.replace(/\.[^/.]+$/, '') || - `Project ${Date.now()}`; - - const newProject = await projectsApi.create(defaultName); - setProject(newProject); - - await projectsApi.uploadImage(newProject.id, imageFile); - setCurrentImageUrl(projectsApi.getCurrentImageUrl(newProject.id)); - setShowProjectSetup(false); - - await loadEdits(newProject.id); - } catch (err) { - setError(`Failed to create project: ${err.message}`); - } finally { - setIsProcessing(false); - } - }; - - // Load edits - const loadEdits = async (projectId) => { - try { - const projectEdits = await projectsApi.getEdits(projectId); - setEdits(projectEdits); - editsRef.current = projectEdits; - const completedEdits = projectEdits.filter(e => e.status === 'completed'); - setCurrentEditIndex(completedEdits.length - 1); - } catch (err) { - console.error('Failed to load edits:', err); - } - }; - - // Poll edit status - const pollEditStatus = async (editId) => { - const maxAttempts = 120; - let attempts = 0; - - const poll = async () => { - try { - const edit = await editsApi.get(editId); - - if (edit.status === 'completed') { - await loadEdits(project.id); - setCurrentImageUrl(projectsApi.getCurrentImageUrl(project.id) + `?t=${Date.now()}`); - setIsProcessing(false); - setSelection(null); - return; - } else if (edit.status === 'failed') { - setError(`Edit failed: ${edit.error_message}`); - setIsProcessing(false); - return; - } - - attempts++; - if (attempts < maxAttempts) { - setTimeout(poll, 1000); - } else { - setError('Edit timeout'); - setIsProcessing(false); - } - } catch (err) { - setError(`Status check failed: ${err.message}`); - setIsProcessing(false); - } - }; - - poll(); - }; - - // Handle AI fix - const handleFix = async () => { - if (!selection || !prompt.trim() || !project) { - setError('Make a selection and enter a prompt'); - return; - } - - try { - setError(null); - setIsProcessing(true); - - const editRequest = { - prompt: prompt.trim(), - mode: mode, - selection_type: selection.type, - bbox: selection.bbox, - feather_px: feather, - selection_data: selection.selectionData, - }; - - const edit = await editsApi.create(project.id, editRequest); - pollEditStatus(edit.id); - } catch (err) { - setError(`Edit failed: ${err.message}`); - setIsProcessing(false); - } - }; - - // Undo/Redo - const handleRevert = useCallback(async (editId) => { - if (!project) return; - try { - setIsProcessing(true); - await editsApi.revert(project.id, editId); - setCurrentImageUrl(projectsApi.getCurrentImageUrl(project.id) + `?t=${Date.now()}`); - await loadEdits(project.id); - } catch (err) { - setError(`Revert failed: ${err.message}`); - } finally { - setIsProcessing(false); - } - }, [project]); - - const handleReset = useCallback(async () => { - if (!project) return; - try { - setIsProcessing(true); - await editsApi.reset(project.id); - setCurrentImageUrl(projectsApi.getCurrentImageUrl(project.id) + `?t=${Date.now()}`); - await loadEdits(project.id); - } catch (err) { - setError(`Reset failed: ${err.message}`); - } finally { - setIsProcessing(false); - } - }, [project]); - - // Download - const handleDownload = async () => { - if (!currentImageUrl) return; - try { - const response = await fetch(`${currentImageUrl}&download=1`); - const blob = await response.blob(); - const url = window.URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.download = `${project?.name || 'image'}-${Date.now()}.png`; - link.click(); - window.URL.revokeObjectURL(url); - } catch (err) { - setError(`Download failed: ${err.message}`); - } - }; - - // Smart Select (SAM) - const handleSmartSelect = async (x, y) => { - if (!project) return; - try { - setIsProcessing(true); - const maskBlob = await toolsApi.smartSelect(project.id, x, y); - // TODO: Display mask on canvas - console.log('Smart select mask:', maskBlob); - } catch (err) { - setError(`Smart select failed: ${err.message}`); - } finally { - setIsProcessing(false); - } - }; - - // Remove background - const handleRemoveBackground = async () => { - if (!project) return; - try { - setIsProcessing(true); - await toolsApi.removeBackgroundToLayer(project.id); - setCurrentImageUrl(projectsApi.getCurrentImageUrl(project.id) + `?t=${Date.now()}`); - } catch (err) { - setError(`Background removal failed: ${err.message}`); - } finally { - setIsProcessing(false); - } - }; - - // Apply eye - const handleApplyEye = async () => { - if (!project || !selection || !selectedEye) { - setError('Select an area and an eye to apply'); - return; - } - try { - setIsProcessing(true); - const formData = new FormData(); - formData.append('project_id', project.id); - formData.append('patch_id', selectedEye.id); - formData.append('bbox', JSON.stringify(selection.bbox)); - formData.append('feather_px', feather); - - await fetch('/patches/apply', { - method: 'POST', - body: formData, - }); - - setCurrentImageUrl(projectsApi.getCurrentImageUrl(project.id) + `?t=${Date.now()}`); - await loadEdits(project.id); - } catch (err) { - setError(`Apply eye failed: ${err.message}`); - } finally { - setIsProcessing(false); - } - }; - - // Keyboard shortcuts - useEffect(() => { - const handleKeyDown = (e) => { - if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; - - // Tool shortcuts - if (!e.ctrlKey && !e.metaKey) { - switch (e.key.toLowerCase()) { - case 'v': setActiveTool('move'); break; - case 'r': setActiveTool('select'); break; - case 'e': setActiveTool('ellipse'); break; - case 'f': setActiveTool('lasso'); break; - case 'w': setActiveTool('magic'); break; - case 'b': setActiveTool('brush'); break; - case 'g': setActiveTool('bucket'); break; - case 'z': setActiveTool('zoom'); break; - case 'h': setActiveTool('pan'); break; - case 'delete': - case 'backspace': - if (selection) { - // TODO: Delete selected area - } - break; - } - } - - // Ctrl shortcuts - if (e.ctrlKey || e.metaKey) { - switch (e.key.toLowerCase()) { - case 'z': - e.preventDefault(); - if (e.shiftKey) { - // Redo - const completed = editsRef.current.filter(ed => ed.status === 'completed'); - if (currentEditIndex < completed.length - 1) { - handleRevert(completed[currentEditIndex + 1].id); - setCurrentEditIndex(i => i + 1); - } - } else { - // Undo - if (currentEditIndex >= 0) { - if (currentEditIndex === 0) { - handleReset(); - } else { - const completed = editsRef.current.filter(ed => ed.status === 'completed'); - handleRevert(completed[currentEditIndex - 1].id); - } - setCurrentEditIndex(i => i - 1); - } - } - break; - case 's': - e.preventDefault(); - handleDownload(); - break; - } - } - }; - - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [selection, currentEditIndex, handleRevert, handleReset]); - - // Zoom controls - const handleZoomIn = () => setZoom(z => Math.min(z + 25, 400)); - const handleZoomOut = () => setZoom(z => Math.max(z - 25, 25)); - const handleZoomReset = () => setZoom(100); - - // Panel toggle - const togglePanel = (panel) => { - setCollapsedPanels(prev => ({ ...prev, [panel]: !prev[panel] })); - }; - - // Project setup overlay - if (showProjectSetup) { - return ( -
-
-
-

Open Image

-
-
- - setImageFile(e.target.files[0])} - disabled={isProcessing} - /> - {imageFile &&

{imageFile.name}

} -
-
- - setProjectName(e.target.value)} - placeholder="Auto-generated from filename" - disabled={isProcessing} - /> -
- -
-
-
-
- ); - } - - return ( -
- {/* Menu Bar */} -
-

AI Photo Edit

-
- - - -
-
- - {/* Error Banner */} - {error && ( -
- {error} - -
- )} - - {/* Main Workspace */} -
- {/* Left Toolbar */} -
- - -
- - - - - - - -
- - - - - -
- - - - -
- - {/* Canvas Area */} -
-
- - {isProcessing && ( -
-
-
- )} -
-
-
- - {zoom}% - - -
- Selection: {selection ? `${selection.bbox?.width || 0}×${selection.bbox?.height || 0}` : 'None'} - Tool: {TOOLS[activeTool]?.name || activeTool} -
-
- - {/* Right Sidebar */} -
- {/* Tool Options Panel */} -
-
togglePanel('toolOptions')}> -

Tool Options

- {collapsedPanels.toolOptions ? '▶' : '▼'} -
-
-
- -
- setFeather(parseInt(e.target.value))} - /> - {feather}px -
-
-
-
- - {/* AI Edit Panel */} -
-
togglePanel('aiEdit')}> -

AI Edit

- {collapsedPanels.aiEdit ? '▶' : '▼'} -
-
-
- -
- - -
-
-
- - '; + } + else { + //text or number + var input_type = "text"; + if (parameter.placeholder != '' && !isNaN(parameter.placeholder)) + input_type = 'number'; + if (parameter.value != undefined && typeof parameter.value == 'number') + input_type = 'number'; + + var comment_html = ''; + if (typeof parameter.comment !== 'undefined') { + comment_html = '' + parameter.comment + ''; + } + + html += ''+comment_html+''; + } + } + } + } + else if (parameter.function != undefined) { + //custom function + var result; + result = parameter.function(); + html += '' + result + ''; + } + else if (parameter.html != undefined) { + //html + html += '' + parameter.html + ''; + } + else if (parameter.title == undefined) { + //gap + html += ''; + } + else { + //locked fields without name + var str = "" + parameter.value; + var id_tmp = parameter.title.toLowerCase().replace(/[^\w]+/g, '').replace(/ +/g, '-'); + id_tmp = id_tmp.substring(0, 10); + if (str.length < 40) + html += '
' + parameter.value + + '
'; + else + html += ''; + } + html += ''; + } + html += ''; + + return html; + } + + //on key press inside input text + onkeyup(event) { + if (event.key == 'Enter') { + if (event.target.hasAttribute('data-prevent-submission')) { + event.preventDefault(); + } else { + this.save(); + } + } + } + + getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + } + + strpos(haystack, needle, offset) { + var i = (haystack + '').indexOf(needle, (offset || 0)); + return i === -1 ? false : i; + } + + draw_background(canvas, W, H, gap, force) { + var transparent = this.Base_gui.get_transparency_support(); + + if (transparent == false && force == undefined) { + canvas.beginPath(); + canvas.rect(0, 0, W, H); + canvas.fillStyle = "#ffffff"; + canvas.fill(); + return false; + } + if (gap == undefined) + gap = 10; + var fill = true; + for (var i = 0; i < W; i = i + gap) { + if (i % (gap * 2) == 0) + fill = true; + else + fill = false; + for (var j = 0; j < H; j = j + gap) { + if (fill == true) { + canvas.fillStyle = '#eeeeee'; + canvas.fillRect(i, j, gap, gap); + fill = false; + } + else + fill = true; + } + } + } + +} + +export default Dialog_class; \ No newline at end of file diff --git a/frontend/src/js/libs/vintage.js b/frontend/src/js/libs/vintage.js new file mode 100644 index 0000000..84fa77d --- /dev/null +++ b/frontend/src/js/libs/vintage.js @@ -0,0 +1,295 @@ +import glfx from './glfx.js'; +import ImageFilters from './imagefilters.js'; + +/** + * adds vintage effect + * + * @author ViliusL + * + * Functions: + * - adjust_color + * - lower_contrast + * - blur + * - light_leak + * - chemicals + * - exposure + * - grains + * - grains_big + * - optics + * - dusts + * + * Usage: VINTAGE.___function___(canvas,, param1, param2, ...); + * + * libs: + * - imagefilters.js, url: https://github.com/arahaya/ImageFilters.js + * - glfx.js url: http://evanw.github.com/glfx.js/ + */ +class Vintage_class { + + constructor(width, height) { + this.fx_filter = false; + this.exposure_rand = null; + this.lightLeakX = null; + this.lightLeakY = null; + + this.reset_random_values(width, height); + } + + /** + * apply all affect + * + * @param {canvas} canvas + * @param {int} level 0-100 + */ + apply_all(canvas, level) { + //adjust from scale [0-100] to our scale. + var red_offset = level * 1; //[0, 100] + var contrast = level / 2; //[0, 50] + //var blur = level / 100; //[0, 1] + var light_leak = level * 1.5; //[0, 150] + var de_saturation = level * 1; //[0, 100] + var exposure = level * 1.5; //[0, 150] + var grains = level / 2; //[0, 50] + var big_grains = level / 5; //[0, 20] + var vignette_size = level / 200; //[0, 0.5] + var vignette_amount = level / 142; //[0, 0.7] + var dust_level = level * 1; //[0, 100] + + this.adjust_color(canvas, red_offset); + this.lower_contrast(canvas, contrast); + //this.blur(canvas, blur); + this.light_leak(canvas, light_leak); + this.chemicals(canvas, de_saturation); + this.exposure(canvas, exposure); + this.grains(canvas, grains); + this.grains_big(canvas, big_grains); + this.optics(canvas, vignette_size, vignette_amount); + this.dusts(canvas, dust_level); + } + + /** + * reset random values again. + * + * @param {int} width + * @param {int} height + */ + reset_random_values(width, height) { + this.exposure_rand = this.getRandomInt(1, 10); + this.lightLeakX = this.getRandomInt(0, width); + this.lightLeakY = this.getRandomInt(0, height); + } + + //increasing red color + adjust_color(canvas, level_red) { //level = [0, 200], default 70 + var context = canvas.getContext("2d"); + var W = canvas.width; + var H = canvas.height; + + var param_green = 0; + var param_blue = 0; + var imageData = context.getImageData(0, 0, W, H); + var filtered = ImageFilters.ColorTransformFilter(imageData, 1, 1, 1, 1, level_red, param_green, param_blue, 1); + context.putImageData(filtered, 0, 0); + } + + //decreasing contrast + lower_contrast(canvas, level) { //level = [0, 50], default 15 + var context = canvas.getContext("2d"); + var W = canvas.width; + var H = canvas.height; + + + var imageData = context.getImageData(0, 0, W, H); + var filtered = ImageFilters.BrightnessContrastPhotoshop(imageData, 0, -level); + context.putImageData(filtered, 0, 0); + } + + //adding blur + blur(canvas, level) { //level = [0, 2], default 0 + var context = canvas.getContext("2d"); + var W = canvas.width; + var H = canvas.height; + + + if (level < 1) + return context; + var imageData = context.getImageData(0, 0, W, H); + var filtered = ImageFilters.GaussianBlur(imageData, level); + context.putImageData(filtered, 0, 0); + } + + //creating transparent #ffa500 radial gradients + light_leak(canvas, level) { //level = [0, 150], default 90 + var context = canvas.getContext("2d"); + var W = canvas.width; + var H = canvas.height; + + var click_x = this.lightLeakX; + var click_y = this.lightLeakY; + var distance = Math.min(W, H) * 0.6; + var radgrad = context.createRadialGradient( + click_x, click_y, distance * level / 255, + click_x, click_y, distance); + radgrad.addColorStop(0, "rgba(255, 165, 0, " + level / 255 + ")"); + radgrad.addColorStop(1, "rgba(255, 255, 255, 0)"); + + context.fillStyle = radgrad; + context.fillRect(0, 0, W, H); + } + + //de-saturate + chemicals(canvas, level) { //level = [0, 100], default 40 + var context = canvas.getContext("2d"); + var W = canvas.width; + var H = canvas.height; + + var imageData = context.getImageData(0, 0, W, H); + var filtered = ImageFilters.HSLAdjustment(imageData, 0, -level, 0); + context.putImageData(filtered, 0, 0); + } + + //creating transparent vertical black-to-white gradients + exposure(canvas, level) { //level = [0, 150], default 80 + var context = canvas.getContext("2d"); + var W = canvas.width; + var H = canvas.height; + + context.rect(0, 0, W, H); + var grd = context.createLinearGradient(0, 0, 0, H); + if (this.exposure_rand < 5) { + //dark at top + grd.addColorStop(0, "rgba(0, 0, 0, " + level / 255 + ")"); + grd.addColorStop(1, "rgba(255, 255, 255, " + level / 255 + ")"); + } + else { + //bright at top + grd.addColorStop(0, "rgba(255, 255, 255, " + level / 255 + ")"); + grd.addColorStop(1, "rgba(0, 0, 0, " + level / 255 + ")"); + } + context.fillStyle = grd; + context.fill(); + } + + //add grains, noise + grains(canvas, level) { //level = [0, 50], default 10 + var context = canvas.getContext("2d"); + var W = canvas.width; + var H = canvas.height; + + if (level == 0) + return context; + var img = context.getImageData(0, 0, W, H); + var imgData = img.data; + for (var j = 0; j < H; j++) { + for (var i = 0; i < W; i++) { + var x = (i + j * W) * 4; + if (imgData[x + 3] == 0) + continue; //transparent + //increase it's lightness + var delta = this.getRandomInt(0, level); + if (delta == 0) + continue; + + if (imgData[x] - delta < 0) + imgData[x] = -(imgData[x] - delta); + else + imgData[x] = imgData[x] - delta; + if (imgData[x + 1] - delta < 0) + imgData[x + 1] = -(imgData[x + 1] - delta); + else + imgData[x + 1] = imgData[x + 1] - delta; + if (imgData[x + 2] - delta < 0) + imgData[x + 2] = -(imgData[x + 2] - delta); + else + imgData[x + 2] = imgData[x + 2] - delta; + } + } + context.putImageData(img, 0, 0); + } + + //add big grains, noise + grains_big(canvas, level) { //level = [0, 50], default 20 + var context = canvas.getContext("2d"); + var W = canvas.width; + var H = canvas.height; + + if (level == 0) + return context; + var n = W * H / 100 * level; //density + var color = 200; + for (var i = 0; i < n; i++) { + var power = this.getRandomInt(5, 10 + level); + var size = 2; + var x = this.getRandomInt(0, W); + var y = this.getRandomInt(0, H); + context.fillStyle = "rgba(" + color + ", " + color + ", " + color + ", " + power / 255 + ")"; + context.fillRect(x, y, size, size); + } + } + + //adding vignette effect - blurred dark borders + optics(canvas, param1, param2) { //param1 [0, 0.5], param2 [0, 0.7], default 0.3, 0.5 + var context = canvas.getContext("2d"); + var W = canvas.width; + var H = canvas.height; + + if (this.fx_filter == false) { + //init glfx lib + this.fx_filter = glfx.canvas(); + } + + var texture = this.fx_filter.texture(context.getImageData(0, 0, W, H)); + this.fx_filter.draw(texture).vignette(param1, param2).update(); + context.drawImage(this.fx_filter, 0, 0); + } + + //add dust and hairs + dusts(canvas, level) { //level = [0, 100], default 70 + var context = canvas.getContext("2d"); + var W = canvas.width; + var H = canvas.height; + + var n = level / 100 * (W * H) / 1000; + //add dust + context.fillStyle = "rgba(200, 200, 200, 0.3)"; + for (var i = 0; i < n; i++) { + var x = this.getRandomInt(0, W); + var y = this.getRandomInt(0, H); + var mode = this.getRandomInt(1, 2); + if (mode == 1) { + var w = 1; + var h = this.getRandomInt(1, 3); + } + else if (mode == 2) { + var w = this.getRandomInt(1, 3); + var h = 1; + } + context.beginPath(); + context.rect(x, y, w, h); + context.fill(); + } + + //add hairs + context.strokeStyle = "rgba(200, 200, 200, 0.2)"; + for (var i = 0; i < n / 20; i++) { + var x = this.getRandomInt(0, W); + var y = this.getRandomInt(0, H); + var radius = this.getRandomInt(5, 10); + var start_nr = this.getRandomInt(0, 20) / 10; + var start_angle = Math.PI * start_nr; + var end_angle = Math.PI * (start_nr + this.getRandomInt(7, 15) / 10); + context.beginPath(); + context.arc(x, y, radius, start_angle, end_angle); + context.stroke(); + } + + return context; + } + + //random number generator + getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + } +} + +export default Vintage_class; \ No newline at end of file diff --git a/frontend/src/js/libs/zoomView.js b/frontend/src/js/libs/zoomView.js new file mode 100644 index 0000000..10c9f4c --- /dev/null +++ b/frontend/src/js/libs/zoomView.js @@ -0,0 +1,150 @@ +//handles zoom and pan +//https://stackoverflow.com/questions/44009094/how-to-bound-image-pan-when-zooming-html-canvas/44015705#44015705 +const zoomView = (() => { + const matrix = [1, 0, 0, 1, 0, 0]; // current view transform + const invMatrix = [1, 0, 0, 1, 0, 0]; // current inverse view transform + var m = matrix; // alias + var im = invMatrix; // alias + var scale = 1; // current scale + const bounds = { + top: 0, + left: 0, + right: 200, + bottom: 200, + }; + var useConstraint = true; // if true then limit pan and zoom to + // keep bounds within the current context + + var maxScale = 1; + const workPoint1 = {x: 0, y: 0}; + const workPoint2 = {x: 0, y: 0}; + const wp1 = workPoint1; // alias + const wp2 = workPoint2; // alias + var ctx; + const pos = {// current position of origin + x: 0, + y: 0, + }; + var dirty = true; + const API = { + canvasDefault() { + ctx.setTransform(1, 0, 0, 1, 0, 0); + }, + apply() { + if (dirty) { + this.update(); + } + ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); + }, + getPosition() { + return { x: pos.x, y: pos.y }; + }, + getContext() { + return ctx; + }, + getBounds() { + return bounds; + }, + getScale() { + return scale; + }, + getMaxScale() { + return maxScale; + }, + matrix, // expose the matrix + invMatrix, // expose the inverse matrix + update() { // call to update transforms + dirty = false; + m[3] = m[0] = scale; + m[1] = m[2] = 0; + m[4] = pos.x; + m[5] = pos.y; + if (useConstraint) { + this.constrain(); + } + this.invScale = 1 / scale; + // calculate the inverse transformation + var cross = m[0] * m[3] - m[1] * m[2]; + im[0] = m[3] / cross; + im[1] = -m[1] / cross; + im[2] = -m[2] / cross; + im[3] = m[0] / cross; + }, + constrain() { + maxScale = Math.min( + ctx.canvas.width / (bounds.right - bounds.left), + ctx.canvas.height / (bounds.bottom - bounds.top) + ); + if (scale < maxScale) { + m[0] = m[3] = scale = maxScale; + } + wp1.x = bounds.left; + wp1.y = bounds.top; + this.toScreen(wp1, wp2); + if (wp2.x > 0) { + m[4] = pos.x -= wp2.x; + } + if (wp2.y > 0) { + m[5] = pos.y -= wp2.y; + } + wp1.x = bounds.right; + wp1.y = bounds.bottom; + this.toScreen(wp1, wp2); + if (wp2.x < ctx.canvas.width) { + m[4] = (pos.x -= wp2.x - ctx.canvas.width); + } + if (wp2.y < ctx.canvas.height) { + m[5] = (pos.y -= wp2.y - ctx.canvas.height); + } + }, + toWorld(from_x, from_y) { // convert screen to world coords + var xx, yy; + var pointW = {}; + if (dirty) { + this.update(); + } + xx = from_x - m[4]; + yy = from_y - m[5]; + pointW.x = xx * im[0] + yy * im[2]; + pointW.y = xx * im[1] + yy * im[3]; + return pointW; + }, + toScreen(from, point = {}){ // convert world coords to screen coords + if (dirty) { + this.update(); + } + point.x = from.x * m[0] + from.y * m[2] + m[4]; + point.y = from.x * m[1] + from.y * m[3] + m[5]; + return point; + }, + scaleAt(x_from, y_from, amount) { // at in screen coords + if (dirty) { + this.update(); + } + scale *= amount; + pos.x = x_from - (x_from - pos.x) * amount; + pos.y = y_from - (y_from - pos.y) * amount; + dirty = true; + }, + move(move_x, move_y) { // move is in screen coords + pos.x += move_x; + pos.y += move_y; + dirty = true; + }, + setContext(context) { + ctx = context; + dirty = true; + }, + setBounds(top, left, right, bottom) { + bounds.top = top; + bounds.left = left; + bounds.right = right; + bounds.bottom = bottom; + useConstraint = true; + dirty = true; + } + }; + return API; +})(); + +export default zoomView; \ No newline at end of file diff --git a/frontend/src/js/main.js b/frontend/src/js/main.js new file mode 100644 index 0000000..9d3eac0 --- /dev/null +++ b/frontend/src/js/main.js @@ -0,0 +1,57 @@ +/** + * miniPaint - https://github.com/viliusle/miniPaint + * author: Vilius L. + */ + +//css +import './../css/reset.css'; +import './../css/utility.css'; +import './../css/component.css'; +import './../css/layout.css'; +import './../css/menu.css'; +import './../css/print.css'; +import './../../node_modules/alertifyjs/build/css/alertify.min.css'; +//js +import app from './app.js'; +import config from './config.js'; +import './core/components/index.js'; +import Base_gui_class from './core/base-gui.js'; +import Base_layers_class from './core/base-layers.js'; +import Base_tools_class from './core/base-tools.js'; +import Base_state_class from './core/base-state.js'; +import Base_search_class from './core/base-search.js'; +import File_open_class from './modules/file/open.js'; +import File_save_class from './modules/file/save.js'; +import * as Actions from './actions/index.js'; + +window.addEventListener('load', function (e) { + // Initiate app + var Layers = new Base_layers_class(); + var Base_tools = new Base_tools_class(true); + var GUI = new Base_gui_class(); + var Base_state = new Base_state_class(); + var File_open = new File_open_class(); + var File_save = new File_save_class(); + var Base_search = new Base_search_class(); + + // Register singletons in app module + app.Actions = Actions; + app.Config = config; + app.FileOpen = File_open; + app.FileSave = File_save; + app.GUI = GUI; + app.Layers = Layers; + app.State = Base_state; + app.Tools = Base_tools; + + // Register as global for quick or external access + window.Layers = Layers; + window.AppConfig = config; + window.State = Base_state; + window.FileOpen = File_open; + window.FileSave = File_save; + + // Render all + GUI.init(); + Layers.init(); +}, false); diff --git a/frontend/src/js/modules/edit/copy.js b/frontend/src/js/modules/edit/copy.js new file mode 100644 index 0000000..1e5c55d --- /dev/null +++ b/frontend/src/js/modules/edit/copy.js @@ -0,0 +1,82 @@ +import config from "../../config"; +import Base_layers_class from './../../core/base-layers.js'; +import File_save_class from './../file/save.js'; +import Helper_class from './../../libs/helpers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +var instance = null; + +class Copy_class { + + constructor() { + //singleton + if (instance) { + return instance; + } + instance = this; + + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.File_save = new File_save_class(); + + //events + document.addEventListener('keydown', (event) => { + var code = event.key.toLowerCase(); + var ctrlDown = event.ctrlKey || event.metaKey; + if (this.Helper.is_input(event.target)) + return; + + if (code == "c" && ctrlDown == true) { + //copy to clipboard + this.copy_to_clipboard(); + } + }, false); + } + + async copy_to_clipboard(){ + var _this = this; + + const canWriteToClipboard = await this.askWritePermission(); + if (canWriteToClipboard) { + + //get data - current layer + var canvas = this.Base_layers.convert_layer_to_canvas(); + var ctx = canvas.getContext("2d"); + + if (config.TRANSPARENCY == false) { + //add white background + ctx.globalCompositeOperation = 'destination-over'; + this.File_save.fillCanvasBackground(ctx, '#ffffff'); + ctx.globalCompositeOperation = 'source-over'; + } + + //save using lib + canvas.toBlob(function (blob) { + _this.setToClipboard(blob); + }); + } + else{ + alertify.error('Missing permissions to write to Clipboard.cc'); + } + } + + async setToClipboard(blob) { + const data = [new ClipboardItem({ [blob.type]: blob })]; + await navigator.clipboard.write(data); + } + + async askWritePermission() { + try { + // The clipboard-write permission is granted automatically to pages + // when they are the active tab. So it's not required, but it's more safe. + const { state } = await navigator.permissions.query({ name: 'clipboard-write' }) + return state === 'granted'; + } + catch (error) { + // Browser compatibility / Security error (ONLY HTTPS) ... + return false; + } + } +} + +export default Copy_class; diff --git a/frontend/src/js/modules/edit/paste.js b/frontend/src/js/modules/edit/paste.js new file mode 100644 index 0000000..acb8ee8 --- /dev/null +++ b/frontend/src/js/modules/edit/paste.js @@ -0,0 +1,10 @@ +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Edit_paste_class { + + paste() { + alertify.error('Use Ctrl+V keyboard shortcut to paste from Clipboard.'); + } +} + +export default Edit_paste_class; diff --git a/frontend/src/js/modules/edit/redo.js b/frontend/src/js/modules/edit/redo.js new file mode 100644 index 0000000..0312b6b --- /dev/null +++ b/frontend/src/js/modules/edit/redo.js @@ -0,0 +1,14 @@ +import Base_state_class from './../../core/base-state.js'; + +class Edit_redo_class { + + constructor() { + this.Base_state = new Base_state_class(); + } + + redo() { + this.Base_state.redo(); + } +} + +export default Edit_redo_class; diff --git a/frontend/src/js/modules/edit/selection.js b/frontend/src/js/modules/edit/selection.js new file mode 100644 index 0000000..bd772ba --- /dev/null +++ b/frontend/src/js/modules/edit/selection.js @@ -0,0 +1,26 @@ +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Selection_class from './../../tools/selection.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Edit_selection_class { + + constructor() { + this.Base_layers = new Base_layers_class(); + this.Selection = new Selection_class(this.Base_layers.ctx); + } + + select_all() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + this.Selection.select_all(); + } + + delete() { + this.Selection.delete_selection(); + } +} + +export default Edit_selection_class; diff --git a/frontend/src/js/modules/edit/undo.js b/frontend/src/js/modules/edit/undo.js new file mode 100644 index 0000000..bae4a85 --- /dev/null +++ b/frontend/src/js/modules/edit/undo.js @@ -0,0 +1,31 @@ +import Base_state_class from './../../core/base-state.js'; + +var instance = null; + +class Edit_undo_class { + + constructor() { + //singleton + if (instance) { + return instance; + } + instance = this; + + this.Base_state = new Base_state_class(); + this.events(); + } + + events(){ + var _this = this; + + document.querySelector('#undo_button').addEventListener('click', function (event) { + _this.Base_state.undo(); + }); + } + + undo() { + this.Base_state.undo(); + } +} + +export default Edit_undo_class; diff --git a/frontend/src/js/modules/effects/abstract/css.js b/frontend/src/js/modules/effects/abstract/css.js new file mode 100644 index 0000000..88c9f70 --- /dev/null +++ b/frontend/src/js/modules/effects/abstract/css.js @@ -0,0 +1,71 @@ +import app from './../../../app.js'; +import config from './../../../config.js'; +import Dialog_class from './../../../libs/popup.js'; +import Base_layers_class from './../../../core/base-layers.js'; +import Helper_class from './../../../libs/helpers.js'; + +class Effects_common_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.params = null; + } + + show_dialog(type, params, filter_id) { + var _this = this; + var title = this.Helper.ucfirst(type); + title = title.replace(/-/g, ' '); + + var preview_padding = 0; + if(typeof this.preview_padding != "undefined"){ + preview_padding = this.preview_padding; + } + + var settings = { + title: title, + preview: true, + preview_padding: preview_padding, + effects: true, + params: params, + on_change: function (params, canvas_preview, w, h) { + _this.params = params; + canvas_preview.filter = _this.preview(params, type); + canvas_preview.drawImage(this.layer_active_small, + preview_padding, preview_padding, + _this.POP.width_mini - preview_padding * 2, _this.POP.height_mini - preview_padding * 2 + ); + }, + on_finish: function (params) { + _this.params = params; + _this.save(params, type, filter_id); + }, + }; + this.Base_layers.disable_filter(filter_id); + this.POP.show(settings); + this.Base_layers.disable_filter(null); + } + + save(params, type, filter_id) { + return app.State.do_action( + new app.Actions.Add_layer_filter_action(null, type, params, filter_id) + ); + } + + preview(params, type) { + if(type == 'shadow'){ + type = 'drop-shadow'; + } + + var value = this.convert_value(params.value, params, 'preview'); + return type + "(" + value + ")"; + } + + convert_value(value, params) { + return value; + } + +} + +export default Effects_common_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/black_and_white.js b/frontend/src/js/modules/effects/black_and_white.js new file mode 100644 index 0000000..6755428 --- /dev/null +++ b/frontend/src/js/modules/effects/black_and_white.js @@ -0,0 +1,219 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Helper_class from './../../libs/helpers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_backAndWhite_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + } + + black_and_white() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //create tmp canvas + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //calc default level + var default_level = this.thresholding(ctx, canvas.width, canvas.height, true); + + var settings = { + title: 'Black and White', + preview: true, + effects: true, + params: [ + {name: "level", title: "Level:", value: default_level, range: [0, 255]}, + {name: "dithering", title: "Dithering:", value: false}, + ], + on_change: function (params, canvas_preview, w, h) { + //check params + var level = document.getElementById("pop_data_level"); + if (params.dithering == false) + level.disabled = false; + else + level.disabled = true; + + var img = canvas_preview.getImageData(0, 0, w, h); + var data = _this.change(img, params); + canvas_preview.putImageData(data, 0, 0); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(data, params) { + var W = data.width; + var H = data.height; + + //create tmp canvas + var canvas = document.createElement('canvas'); + canvas.width = W; + canvas.height = H; + + var imgData = data.data; + var grey, c, quant_error, m; + if (params.dithering !== true) { + //no differing + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + grey = Math.round(0.2126 * imgData[i] + 0.7152 * imgData[i + 1] + 0.0722 * imgData[i + 2]); + if (grey <= params.level) + c = 0; + else + c = 255; + imgData[i] = c; + imgData[i + 1] = c; + imgData[i + 2] = c; + } + } + else { + //Floyd–Steinberg dithering + var img2 = canvas.getContext("2d").getImageData(0, 0, W, H); + var imgData2 = img2.data; + for (var j = 0; j < H; j++) { + for (var i = 0; i < W; i++) { + var k = ((j * (W * 4)) + (i * 4)); + if (imgData[k + 3] == 0) + continue; //transparent + + grey = Math.round(0.2126 * imgData[k] + 0.7152 * imgData[k + 1] + 0.0722 * imgData[k + 2]); + grey = grey + imgData2[k]; //add data shft from previous iterations + c = Math.floor(grey / 256); + if (c == 1) + c = 255; + imgData[k] = c; + imgData[k + 1] = c; + imgData[k + 2] = c; + quant_error = grey - c; + if (i + 1 < W) { + m = k + 4; + imgData2[m] += Math.round(quant_error * 7 / 16); + } + if (i - 1 > 0 && j + 1 < H) { + m = k - 4 + W * 4; + imgData2[m] += Math.round(quant_error * 3 / 16); + } + if (j + 1 < H) { + m = k + W * 4; + imgData2[m] += Math.round(quant_error * 5 / 16); + } + if (i + 1 < W && j + 1 < H) { + m = k + 4 + W * 4; + imgData2[m] += Math.round(quant_error * 1 / 16); + } + } + } + } + return data; + } + + thresholding(ctx, W, H, only_level) { + var img = ctx.getImageData(0, 0, W, H); + var imgData = img.data; + var hist_data = []; + var grey; + for (var i = 0; i <= 255; i++) + hist_data[i] = 0; + for (var i = 0; i < imgData.length; i += 4) { + grey = Math.round(0.2126 * imgData[i] + 0.7152 * imgData[i + 1] + 0.0722 * imgData[i + 2]); + hist_data[grey]++; + } + var level = this.otsu(hist_data, W * H); + if (only_level === true) + return level; + var c; + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + grey = Math.round(0.2126 * imgData[i] + 0.7152 * imgData[i + 1] + 0.0722 * imgData[i + 2]); + if (grey < level) + c = 0; + else + c = 255; + imgData[i] = c; + imgData[i + 1] = c; + imgData[i + 2] = c; + } + ctx.putImageData(img, 0, 0); + } + + //http://en.wikipedia.org/wiki/Otsu%27s_Method + otsu(histogram, total) { + var sum = 0; + for (var i = 1; i < 256; ++i) + sum += i * histogram[i]; + var mB, mF, between; + var sumB = 0; + var wB = 0; + var wF = 0; + var max = 0; + var threshold = 0; + for (var i = 0; i < 256; ++i) { + wB += histogram[i]; + if (wB == 0) + continue; + wF = total - wB; + if (wF == 0) + break; + sumB += i * histogram[i]; + mB = sumB / wB; + mF = (sum - sumB) / wF; + between = wB * wF * Math.pow(mB - mF, 2); + if (between > max) { + max = between; + threshold = i; + } + } + return threshold; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var img = ctx.getImageData(0, 0, canvas_thumb.width, canvas_thumb.height); + var default_level = this.thresholding(ctx, canvas_thumb.width, canvas_thumb.height, true); + var params = { + level: default_level, + dithering: false, + } + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + } + +} + +export default Effects_backAndWhite_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/blueprint.js b/frontend/src/js/modules/effects/blueprint.js new file mode 100644 index 0000000..7a60163 --- /dev/null +++ b/frontend/src/js/modules/effects/blueprint.js @@ -0,0 +1,157 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import ImageFilters from './../../libs/imagefilters.js'; +import glfx from './../../libs/glfx.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_blueprint_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.ImageFilters = ImageFilters; + this.fx_filter = false; + } + + blueprint() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, width, height) { + if (this.fx_filter == false) { + //init glfx lib + this.fx_filter = glfx.canvas(); + } + var ctx = canvas.getContext("2d"); + + //create blue layer + var canvas2 = document.createElement('canvas'); + var ctx2 = canvas2.getContext("2d"); + canvas2.width = width; + canvas2.height = height; + ctx2.fillStyle = '#0e58a3'; + ctx2.fillRect(0, 0, width, height); + + //apply edges + var img = ctx.getImageData(0, 0, width, height); + var img = this.ImageFilters.Edge(img); + ctx.putImageData(img, 0, 0); + + //denoise + var texture = this.fx_filter.texture(canvas); + this.fx_filter.draw(texture).denoise(20).update(); //effect + canvas = this.fx_filter; + + //Brightness + var img = ctx.getImageData(0, 0, width, height); + var img = this.ImageFilters.BrightnessContrastPhotoshop(img, 80, 0); + ctx.putImageData(img, 0, 0); + + //merge + ctx2.globalCompositeOperation = "screen"; + ctx2.filter = 'grayscale(1)'; + ctx2.drawImage(canvas, 0, 0); + ctx2.globalCompositeOperation = "source-over"; + ctx2.filter = 'none'; + + //draw lines + this.draw_grid(ctx2, 20); + + return canvas2; + } + + /** + * draw grid + * + * @param {CanvasContext} ctx + * @param {Int} size + */ + draw_grid(ctx, size) { + if (this.grid == false) + return; + + var width = config.WIDTH; + var height = config.HEIGHT; + var color_main = 'rgba(255, 255, 255, 0.5)'; + var color_small = 'rgba(255, 255, 255, 0.1)'; + + //size + if (size != undefined && size != undefined) + this.grid_size = [size, size]; + else { + size = this.grid_size[0]; + size = this.grid_size[1]; + } + size = parseInt(size); + size = parseInt(size); + ctx.lineWidth = 1; + ctx.beginPath(); + if (size < 2) + size = 2; + if (size < 2) + size = 2; + for (var i = size; i < width; i = i + size) { + if (size == 0) + break; + if (i % (size * 5) == 0) { + //main lines + ctx.strokeStyle = color_main; + } + else { + //small lines + ctx.strokeStyle = color_small; + } + ctx.beginPath(); + ctx.moveTo(0.5 + i, 0); + ctx.lineTo(0.5 + i, height); + ctx.stroke(); + } + for (var i = size; i < height; i = i + size) { + if (size == 0) + break; + if (i % (size * 5) == 0) { + //main lines + ctx.strokeStyle = color_main; + } + else { + //small lines + ctx.strokeStyle = color_small; + } + ctx.beginPath(); + ctx.moveTo(0, 0.5 + i); + ctx.lineTo(width, 0.5 + i); + ctx.stroke(); + } + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var data = this.change(canvas, canvas_thumb.width, canvas_thumb.height); + ctx.drawImage(data, 0, 0); + } +} + +export default Effects_blueprint_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/borders.js b/frontend/src/js/modules/effects/borders.js new file mode 100644 index 0000000..0ba3ddb --- /dev/null +++ b/frontend/src/js/modules/effects/borders.js @@ -0,0 +1,107 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Dialog_class from './../../libs/popup.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; +import Effects_browser_class from "./browser"; + +class Effects_borders_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Effects_browser = new Effects_browser_class(); + } + + borders(filter_id) { + if (config.layer.type == null) { + alertify.error('Layer is empty.'); + return; + } + + var _this = this; + var filter = this.Base_layers.find_filter_by_id(filter_id, 'borders'); + + var settings = { + title: 'Borders', + params: [ + {name: "color", title: "Color:", value: filter.color ??= config.COLOR, type: 'color'}, + {name: "size", title: "Size:", value: filter.size ??= 10}, + ], + on_finish: function (params) { + var target = Math.min(config.WIDTH, config.HEIGHT); + _this.add_borders(params, filter_id); + }, + }; + var rotate = config.layer.rotate; + config.layer.rotate = 0; + this.Base_layers.disable_filter(filter_id); + this.POP.show(settings); + config.layer.rotate = rotate; + this.Base_layers.disable_filter(null); + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //draw + ctx.drawImage(canvas_thumb, + 5, 5, + this.Effects_browser.preview_width - 10, this.Effects_browser.preview_height - 10); + + //add borders + ctx.strokeStyle = '#000000'; + ctx.lineWidth = 10; + ctx.beginPath(); + ctx.rect(0, 0, canvas.width, canvas.height); + ctx.stroke(); + } + + render_pre(ctx, data) { + + } + + render_post(ctx, data, layer){ + var size = Math.max(0, data.params.size); + + var x = layer.x; + var y = layer.y; + var width = parseInt(layer.width); + var height = parseInt(layer.height); + + //legacy check + if(x == null) x = 0; + if(y == null) y = 0; + if(!width) width = config.WIDTH; + if(!height) height = config.HEIGHT; + + ctx.save(); + + //set styles + ctx.strokeStyle = data.params.color; + ctx.lineWidth = size; + + //draw with rotation support + ctx.translate(layer.x + width / 2, layer.y + height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + var x_new = -width / 2; + var y_new = -height / 2; + + ctx.beginPath(); + ctx.rect(x_new - size * 0.5, y_new - size * 0.5, width + size, height + size); + ctx.stroke(); + + ctx.restore(); + } + + add_borders(params, filter_id) { + //apply effect + return app.State.do_action( + new app.Actions.Add_layer_filter_action(config.layer.id, 'borders', params, filter_id) + ); + } + +} + +export default Effects_borders_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/box_blur.js b/frontend/src/js/modules/effects/box_blur.js new file mode 100644 index 0000000..e3dc1f2 --- /dev/null +++ b/frontend/src/js/modules/effects/box_blur.js @@ -0,0 +1,88 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import ImageFilters from './../../libs/imagefilters.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_boxBlur_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + box_blur() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Box blur', + preview: true, + effects: true, + params: [ + {name: "param1", title: "H Radius:", value: 3, range: [1, 20]}, + {name: "param2", title: "V Radius:", value: 3, range: [1, 20]}, + {name: "param3", title: "Quality:", value: 3, range: [1, 20]}, + ], + on_change: function (params, canvas_preview, w, h) { + var img = canvas_preview.getImageData(0, 0, w, h); + var data = _this.change(img, params); + canvas_preview.putImageData(data, 0, 0); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(data, params) { + var param1 = params.param1; + var param2 = params.param2; + var param3 = params.param3; + + var filtered = ImageFilters.BoxBlur(data, param1, param2, param3); + + return filtered; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var img = ctx.getImageData(0, 0, canvas_thumb.width, canvas_thumb.height); + var params = { + param1: 20, + param2: 1, + param3: 1, + } + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + } + +} + +export default Effects_boxBlur_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/browser.js b/frontend/src/js/modules/effects/browser.js new file mode 100644 index 0000000..ed57f03 --- /dev/null +++ b/frontend/src/js/modules/effects/browser.js @@ -0,0 +1,140 @@ +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Dialog_class from './../../libs/popup.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_browser_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.POP = new Dialog_class(); + this.preview_width = 150; + this.preview_height = 120; + } + + async browser() { + var _this = this; + var html = ''; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var data = this.get_effects_list(); + + for (var i in data) { + var title = data[i].title; + + html += '
'; + html += ' '; + html += '
' + title + '
'; + html += '
'; + } + for (var i = 0; i < 4; i++) { + html += '
'; + } + + var settings = { + title: 'Effects browser', + className: 'wide', + on_load: function (params, popup) { + var node = document.createElement("div"); + node.classList.add('flex-container'); + node.innerHTML = html; + popup.el.querySelector('.dialog_content').appendChild(node); + //events + var targets = popup.el.querySelectorAll('.item canvas'); + for (var i = 0; i < targets.length; i++) { + targets[i].addEventListener('click', function (event) { + //we have click + var key = this.dataset.key; + for (var i in data) { + if(data[i].key == key){ + var function_name = _this.get_function_from_path(key); + _this.POP.hide(); + data[i].object[function_name](); + } + } + }); + } + }, + }; + this.POP.show(settings); + + //sleep, lets wait till DOM is finished + await new Promise(r => setTimeout(r, 10)); + + //generate thumb + var active_image = this.Base_layers.convert_layer_to_canvas(); + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext("2d"); + canvas.width = this.preview_width; + canvas.height = this.preview_height; + + ctx.scale(this.preview_width / active_image.width, this.preview_height / active_image.height); + ctx.drawImage(active_image, 0, 0); + ctx.scale(1, 1); + + //draw demo thumbs + for (var i in data) { + var title = data[i].title; + var function_name = 'demo'; + if(typeof data[i].object[function_name] == "undefined") + continue; + data[i].object[function_name]('c_'+data[i].key, canvas); + } + } + + get_effects_list() { + var list = []; + + for (var i in this.Base_gui.modules) { + if (i.indexOf("effects") == -1 || i.indexOf("abstract") > -1 || i.indexOf("browser") > -1) + continue; + + list.push({ + title: this.get_filter_title(i), + key: i, + object: this.Base_gui.modules[i], + }); + } + + list.sort(function(a, b) { + var nameA = a.title.toUpperCase(); + var nameB = b.title.toUpperCase(); + if (nameA < nameB) return -1; + if (nameA > nameB) return 1; + return 0; + }); + + return list; + } + + get_filter_title(key) { + var parts = key.split("/"); + var title = parts[parts.length - 1]; + + //exceptions + if (title == 'negative') + title = 'invert'; + + title = title.replace(/_/g, ' '); + title = title.charAt(0).toUpperCase() + title.slice(1); //make first letter uppercase + + return title; + } + + get_function_from_path(path){ + var parts = path.split("/"); + var result = parts[parts.length - 1]; + result = result.replace(/-/, '_'); + + return result; + } +} + +export default Effects_browser_class; diff --git a/frontend/src/js/modules/effects/common/blur.js b/frontend/src/js/modules/effects/common/blur.js new file mode 100644 index 0000000..254efc0 --- /dev/null +++ b/frontend/src/js/modules/effects/common/blur.js @@ -0,0 +1,68 @@ +import config from '../../../config.js'; +import Effects_common_class from '../abstract/css.js'; +import Dialog_class from '../../../libs/popup.js'; +import Base_layers_class from './../../../core/base-layers.js'; +import alertify from './../../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_blur_class extends Effects_common_class { + + constructor() { + super(); + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + blur(filter_id) { + if (config.layer.type == null) { + alertify.error('Layer is empty.'); + return; + } + + var filter = this.Base_layers.find_filter_by_id(filter_id, 'blur'); + + var params = [ + {name: "value", title: "Percentage:", value: filter.value ??= 5, range: [0, 50]}, + ]; + this.show_dialog('blur', params, filter_id); + } + + convert_value(value, params, type) { + + //adapt size to real canvas dimensions + if (type == 'preview') { + var diff = (this.POP.width_mini / this.POP.height_mini) / (config.WIDTH / config.HEIGHT); + + value = value * diff; + } + + return value + 'px'; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //draw + var size = this.convert_value(5, null, 'preview'); + ctx.filter = "blur("+size+")"; + ctx.drawImage(canvas_thumb, 0, 0); + ctx.filter = 'none'; + } + + render_pre(ctx, data) { + var value = this.convert_value(data.params.value, data.params, 'save'); + var filter = 'blur(' + value + ')'; + + if(ctx.filter == 'none') + ctx.filter = filter; + else + ctx.filter += ' ' + filter; + } + + render_post(ctx, data){ + ctx.filter = 'none'; + } + +} + +export default Effects_blur_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/common/brightness.js b/frontend/src/js/modules/effects/common/brightness.js new file mode 100644 index 0000000..2fc9368 --- /dev/null +++ b/frontend/src/js/modules/effects/common/brightness.js @@ -0,0 +1,68 @@ +import Effects_common_class from '../abstract/css.js'; +import Base_layers_class from './../../../core/base-layers.js'; +import config from "../../../config"; +import alertify from './../../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_brightness_class extends Effects_common_class { + + constructor() { + super(); + this.Base_layers = new Base_layers_class(); + } + + brightness(filter_id) { + if (config.layer.type == null) { + alertify.error('Layer is empty.'); + return; + } + var filter = this.Base_layers.find_filter_by_id(filter_id, 'brightness'); + + var params = [ + {name: "value", title: "Percentage:", value: filter.value ??= 50, range: [-100, 100]}, + ]; + this.show_dialog('brightness', params, filter_id); + } + + convert_value(value) { + var system_value; + if (value > 0) { + system_value = value / 100 + 1; + } + else if (value < 0) { + system_value = value / 100 + 1; + } + else { + system_value = 1; + } + + return system_value; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //draw + var size = this.convert_value(30, null, 'preview'); + ctx.filter = "brightness("+size+")"; + ctx.drawImage(canvas_thumb, 0, 0); + ctx.filter = 'none'; + } + + render_pre(ctx, data) { + var value = this.convert_value(data.params.value, data.params, 'save'); + var filter = 'brightness(' + value + ')'; + + if(ctx.filter == 'none') + ctx.filter = filter; + else + ctx.filter += ' ' + filter; + } + + render_post(ctx, data){ + ctx.filter = 'none'; + } + +} + +export default Effects_brightness_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/common/contrast.js b/frontend/src/js/modules/effects/common/contrast.js new file mode 100644 index 0000000..6f9126a --- /dev/null +++ b/frontend/src/js/modules/effects/common/contrast.js @@ -0,0 +1,69 @@ +import Effects_common_class from '../abstract/css.js'; +import Base_layers_class from './../../../core/base-layers.js'; +import config from "../../../config"; +import alertify from './../../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_contrast_class extends Effects_common_class { + + constructor() { + super(); + this.Base_layers = new Base_layers_class(); + } + + contrast(filter_id) { + if (config.layer.type == null) { + alertify.error('Layer is empty.'); + return; + } + + var filter = this.Base_layers.find_filter_by_id(filter_id, 'contrast'); + + var params = [ + {name: "value", title: "Percentage:", value: filter.value ??= 40, range: [-100, 100]}, + ]; + this.show_dialog('contrast', params, filter_id); + } + + convert_value(value) { + var system_value; + if (value > 0) { + system_value = value / 100 + 1; + } + else if (value < 0) { + system_value = value / 100 + 1; + } + else { + system_value = 1; + } + + return system_value; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //draw + var size = this.convert_value(40, null, 'preview'); + ctx.filter = "contrast("+size+")"; + ctx.drawImage(canvas_thumb, 0, 0); + ctx.filter = 'none'; + } + + render_pre(ctx, data) { + var value = this.convert_value(data.params.value, data.params, 'save'); + var filter = 'contrast(' + value + ')'; + + if(ctx.filter == 'none') + ctx.filter = filter; + else + ctx.filter += ' ' + filter; + } + + render_post(ctx, data){ + ctx.filter = 'none'; + } + +} + +export default Effects_contrast_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/common/grayscale.js b/frontend/src/js/modules/effects/common/grayscale.js new file mode 100644 index 0000000..a811520 --- /dev/null +++ b/frontend/src/js/modules/effects/common/grayscale.js @@ -0,0 +1,60 @@ +import Effects_common_class from '../abstract/css.js'; +import Base_layers_class from './../../../core/base-layers.js'; +import config from "../../../config"; +import alertify from './../../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_grayscale_class extends Effects_common_class { + + constructor() { + super(); + this.Base_layers = new Base_layers_class(); + } + + grayscale(filter_id) { + if (config.layer.type == null) { + alertify.error('Layer is empty.'); + return; + } + + var filter = this.Base_layers.find_filter_by_id(filter_id, 'grayscale'); + + var params = [ + {name: "value", title: "Percentage:", value: filter.value ??= 100, range: [0, 100]}, + ]; + this.show_dialog('grayscale', params, filter_id); + } + + convert_value(value) { + var system_value = value / 100; + + return system_value; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //draw + var size = this.convert_value(100, null, 'preview'); + ctx.filter = "grayscale("+size+")"; + ctx.drawImage(canvas_thumb, 0, 0); + ctx.filter = 'none'; + } + + render_pre(ctx, data) { + var value = this.convert_value(data.params.value, data.params, 'save'); + var filter = 'grayscale(' + value + ')'; + + if(ctx.filter == 'none') + ctx.filter = filter; + else + ctx.filter += ' ' + filter; + } + + render_post(ctx, data){ + ctx.filter = 'none'; + } + +} + +export default Effects_grayscale_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/common/hue-rotate.js b/frontend/src/js/modules/effects/common/hue-rotate.js new file mode 100644 index 0000000..cff185a --- /dev/null +++ b/frontend/src/js/modules/effects/common/hue-rotate.js @@ -0,0 +1,58 @@ +import Effects_common_class from '../abstract/css.js'; +import Base_layers_class from './../../../core/base-layers.js'; +import config from "../../../config"; +import alertify from './../../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_hueRotate_class extends Effects_common_class { + + constructor() { + super(); + this.Base_layers = new Base_layers_class(); + } + + hue_rotate(filter_id) { + if (config.layer.type == null) { + alertify.error('Layer is empty.'); + return; + } + + var filter = this.Base_layers.find_filter_by_id(filter_id, 'hue-rotate'); + + var params = [ + {name: "value", title: "Degree:", value: filter.value ??= 90, range: [0, 360]}, + ]; + this.show_dialog('hue-rotate', params, filter_id); + } + + convert_value(value) { + return value + 'deg'; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //draw + var size = this.convert_value(90, null, 'preview'); + ctx.filter = "hue-rotate("+size+")"; + ctx.drawImage(canvas_thumb, 0, 0); + ctx.filter = 'none'; + } + + render_pre(ctx, data) { + var value = this.convert_value(data.params.value, data.params, 'save'); + var filter = 'hue-rotate(' + value + ')'; + + if(ctx.filter == 'none') + ctx.filter = filter; + else + ctx.filter += ' ' + filter; + } + + render_post(ctx, data){ + ctx.filter = 'none'; + } + +} + +export default Effects_hueRotate_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/common/invert.js b/frontend/src/js/modules/effects/common/invert.js new file mode 100644 index 0000000..f3ffcb0 --- /dev/null +++ b/frontend/src/js/modules/effects/common/invert.js @@ -0,0 +1,59 @@ +import Effects_common_class from '../abstract/css.js'; +import Base_layers_class from './../../../core/base-layers.js'; +import config from "../../../config"; +import alertify from './../../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_invert_class extends Effects_common_class { + + constructor() { + super(); + this.Base_layers = new Base_layers_class(); + } + + invert(filter_id) { + if (config.layer.type == null) { + alertify.error('Layer is empty.'); + return; + } + + var filter = this.Base_layers.find_filter_by_id(filter_id, 'invert'); + + var params = [ + {name: "value", title: "Percentage:", value: filter.value ??= 100, range: [0, 100]}, + ]; + this.show_dialog('invert', params, filter_id); + } + + convert_value(value) { + var system_value = value / 100; + return system_value; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //draw + var size = this.convert_value(100, null, 'preview'); + ctx.filter = "invert("+size+")"; + ctx.drawImage(canvas_thumb, 0, 0); + ctx.filter = 'none'; + } + + render_pre(ctx, data) { + var value = this.convert_value(data.params.value, data.params, 'save'); + var filter = 'invert(' + value + ')'; + + if(ctx.filter == 'none') + ctx.filter = filter; + else + ctx.filter += ' ' + filter; + } + + render_post(ctx, data){ + ctx.filter = 'none'; + } + +} + +export default Effects_invert_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/common/saturate.js b/frontend/src/js/modules/effects/common/saturate.js new file mode 100644 index 0000000..05ccc98 --- /dev/null +++ b/frontend/src/js/modules/effects/common/saturate.js @@ -0,0 +1,69 @@ +import Effects_common_class from '../abstract/css.js'; +import Base_layers_class from './../../../core/base-layers.js'; +import config from "../../../config"; +import alertify from './../../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_saturate_class extends Effects_common_class { + + constructor() { + super(); + this.Base_layers = new Base_layers_class(); + } + + saturate(filter_id) { + if (config.layer.type == null) { + alertify.error('Layer is empty.'); + return; + } + + var filter = this.Base_layers.find_filter_by_id(filter_id, 'saturate'); + + var params = [ + {name: "value", title: "Percentage:", value: filter.value ??= -50, range: [-100, 100]}, + ]; + this.show_dialog('saturate', params, filter_id); + } + + convert_value(value) { + var system_value; + if (value > 0) { + system_value = value / 100 + 1; + } + else if (value < 0) { + system_value = value / 100 + 1; + } + else { + system_value = 1; + } + + return system_value; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //draw + var size = this.convert_value(-50, null, 'preview'); + ctx.filter = "saturate("+size+")"; + ctx.drawImage(canvas_thumb, 0, 0); + ctx.filter = 'none'; + } + + render_pre(ctx, data) { + var value = this.convert_value(data.params.value, data.params, 'save'); + var filter = 'saturate(' + value + ')'; + + if(ctx.filter == 'none') + ctx.filter = filter; + else + ctx.filter += ' ' + filter; + } + + render_post(ctx, data){ + ctx.filter = 'none'; + } + +} + +export default Effects_saturate_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/common/sepia.js b/frontend/src/js/modules/effects/common/sepia.js new file mode 100644 index 0000000..0e8e15b --- /dev/null +++ b/frontend/src/js/modules/effects/common/sepia.js @@ -0,0 +1,60 @@ +import Effects_common_class from '../abstract/css.js'; +import Base_layers_class from './../../../core/base-layers.js'; +import config from "../../../config"; +import alertify from './../../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_sepia_class extends Effects_common_class { + + constructor() { + super(); + this.Base_layers = new Base_layers_class(); + } + + sepia(filter_id) { + if (config.layer.type == null) { + alertify.error('Layer is empty.'); + return; + } + + var filter = this.Base_layers.find_filter_by_id(filter_id, 'sepia'); + + var params = [ + {name: "value", title: "Percentage:", value: filter.value ??= 60, range: [0, 100]}, + ]; + this.show_dialog('sepia', params, filter_id); + } + + convert_value(value) { + var system_value = value / 100; + + return system_value; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //draw + var size = this.convert_value(60, null, 'preview'); + ctx.filter = "sepia("+size+")"; + ctx.drawImage(canvas_thumb, 0, 0); + ctx.filter = 'none'; + } + + render_pre(ctx, data) { + var value = this.convert_value(data.params.value, data.params, 'save'); + var filter = 'sepia(' + value + ')'; + + if(ctx.filter == 'none') + ctx.filter = filter; + else + ctx.filter += ' ' + filter; + } + + render_post(ctx, data){ + ctx.filter = 'none'; + } + +} + +export default Effects_sepia_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/common/shadow.js b/frontend/src/js/modules/effects/common/shadow.js new file mode 100644 index 0000000..e074ce1 --- /dev/null +++ b/frontend/src/js/modules/effects/common/shadow.js @@ -0,0 +1,79 @@ +import config from '../../../config.js'; +import Effects_common_class from '../abstract/css.js'; +import Dialog_class from '../../../libs/popup.js'; +import Effects_browser_class from '../browser.js'; +import Base_layers_class from './../../../core/base-layers.js'; +import alertify from './../../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_brightness_class extends Effects_common_class { + + constructor() { + super(); + this.POP = new Dialog_class(); + this.Effects_browser = new Effects_browser_class(); + this.Base_layers = new Base_layers_class(); + this.preview_padding = 20; + } + + shadow(filter_id) { + if (config.layer.type == null) { + alertify.error('Layer is empty.'); + return; + } + + var filter = this.Base_layers.find_filter_by_id(filter_id, 'shadow'); + + var params = [ + {name: "x", title: "Offset X:", value: filter.x ??= 10, range: [-100, 100]}, + {name: "y", title: "Offset Y:", value: filter.y ??= 10, range: [-100, 100]}, + {name: "value", title: "Radius:", value: filter.value ??= 5, range: [0, 100]}, + {name: "color", title: "Color:", value: filter.color ??= "#000000", type: 'color'}, + ]; + this.show_dialog('shadow', params, filter_id); + } + + convert_value(value, params, type) { + var system_value = value; + + //adapt size to real canvas dimensions + if (type == 'preview') { + var diff = (this.POP.width_mini / this.POP.height_mini) / (config.WIDTH / config.HEIGHT); + + params.x = params.x * (this.POP.width_mini / config.WIDTH); + params.y = params.y * (this.POP.height_mini / config.HEIGHT); + params.value = params.value * diff; + } + + return params.x + "px " + params.y + "px " + params.value + "px " + params.color; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //draw + var size = this.convert_value(null, {x: 5, y: 5, value: 5, color: '#000000'}, 'preview'); + ctx.filter = "drop-shadow("+size+")"; + ctx.drawImage(canvas_thumb, + 10, 10, + this.Effects_browser.preview_width - 20, this.Effects_browser.preview_height - 20); + ctx.filter = 'none'; + } + + render_pre(ctx, data) { + var value = this.convert_value(data.params.value, data.params, 'save'); + var filter = 'drop-shadow(' + value + ')'; + + if(ctx.filter == 'none') + ctx.filter = filter; + else + ctx.filter += ' ' + filter; + } + + render_post(ctx, data){ + ctx.filter = 'none'; + } + +} + +export default Effects_brightness_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/denoise.js b/frontend/src/js/modules/effects/denoise.js new file mode 100644 index 0000000..f28d60c --- /dev/null +++ b/frontend/src/js/modules/effects/denoise.js @@ -0,0 +1,89 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import glfx from './../../libs/glfx.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_denoise_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.fx_filter = false; + } + + denoise() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Denoise', + preview: true, + effects: true, + params: [ + {name: "param1", title: "Exponent:", value: 20, range: [0, 50]}, + ], + on_change: function (params, canvas_preview, w, h, canvas_) { + var data = _this.change(canvas_, params); + canvas_preview.clearRect(0, 0, canvas_.width, canvas_.height); + canvas_preview.drawImage(data, 0, 0); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, params); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, params) { + if (this.fx_filter == false) { + //init glfx lib + this.fx_filter = glfx.canvas(); + } + + var param1 = parseFloat(params.param1); + + var texture = this.fx_filter.texture(canvas); + this.fx_filter.draw(texture).denoise(param1).update(); //effect + + return this.fx_filter; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var params = { + param1: 20, + }; + var data = this.change(canvas_thumb, params); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_denoise_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/dither.js b/frontend/src/js/modules/effects/dither.js new file mode 100644 index 0000000..45ffa9a --- /dev/null +++ b/frontend/src/js/modules/effects/dither.js @@ -0,0 +1,82 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import ImageFilters from './../../libs/imagefilters.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_dither_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + dither() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Dither', + preview: true, + effects: true, + params: [ + {name: "param1", title: "Levels:", value: "8", range: [2, 32]}, + ], + on_change: function (params, canvas_preview, w, h) { + var img = canvas_preview.getImageData(0, 0, w, h); + var data = _this.change(img, params); + canvas_preview.putImageData(data, 0, 0); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(data, params) { + var param1 = parseFloat(params.param1); + + var filtered = ImageFilters.Dither(data, param1); + + return filtered; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var img = ctx.getImageData(0, 0, canvas_thumb.width, canvas_thumb.height); + var params = { + param1: 8, + } + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + } + +} + +export default Effects_dither_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/dot_screen.js b/frontend/src/js/modules/effects/dot_screen.js new file mode 100644 index 0000000..aebe463 --- /dev/null +++ b/frontend/src/js/modules/effects/dot_screen.js @@ -0,0 +1,89 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import glfx from './../../libs/glfx.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_dotScreen_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.fx_filter = false; + } + + dot_screen() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Dot Screen', + preview: true, + effects: true, + params: [ + {name: "size", title: "Size:", value: "3", range: [1, 20]}, + ], + on_change: function (params, canvas_preview, w, h, canvas_) { + var data = _this.change(canvas_, params); + canvas_preview.clearRect(0, 0, canvas_.width, canvas_.height); + canvas_preview.drawImage(data, 0, 0); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, params); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, params) { + if (this.fx_filter == false) { + //init glfx lib + this.fx_filter = glfx.canvas(); + } + + var size = parseFloat(params.size); + + var texture = this.fx_filter.texture(canvas); + this.fx_filter.draw(texture).dotScreen(Math.round(canvas.width / 2), Math.round(canvas.height / 2), 0, size).update(); + + return this.fx_filter; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var params = { + size: 3, + }; + var data = this.change(canvas_thumb, params); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_dotScreen_class; diff --git a/frontend/src/js/modules/effects/edge.js b/frontend/src/js/modules/effects/edge.js new file mode 100644 index 0000000..e5c1081 --- /dev/null +++ b/frontend/src/js/modules/effects/edge.js @@ -0,0 +1,55 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import ImageFilters from './../../libs/imagefilters.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_edge_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + edge() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.change(img); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(data) { + var filtered = ImageFilters.Edge(data); + + return filtered; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var img = ctx.getImageData(0, 0, canvas_thumb.width, canvas_thumb.height); + var data = this.change(img); + ctx.putImageData(data, 0, 0); + } + +} + +export default Effects_edge_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/emboss.js b/frontend/src/js/modules/effects/emboss.js new file mode 100644 index 0000000..281305c --- /dev/null +++ b/frontend/src/js/modules/effects/emboss.js @@ -0,0 +1,55 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import ImageFilters from './../../libs/imagefilters.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_emboss_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + emboss() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.change(img); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(data) { + var filtered = ImageFilters.Emboss(data); + + return filtered; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var img = ctx.getImageData(0, 0, canvas_thumb.width, canvas_thumb.height); + var data = this.change(img); + ctx.putImageData(data, 0, 0); + } + +} + +export default Effects_emboss_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/enrich.js b/frontend/src/js/modules/effects/enrich.js new file mode 100644 index 0000000..9b904e5 --- /dev/null +++ b/frontend/src/js/modules/effects/enrich.js @@ -0,0 +1,76 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import ImageFilters from './../../libs/imagefilters.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_enrich_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + enrich() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Enrich', + preview: true, + effects: true, + params: [], + on_change: function (params, canvas_preview, w, h) { + var img = canvas_preview.getImageData(0, 0, w, h); + var data = _this.change(img, params); + canvas_preview.putImageData(data, 0, 0); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(data, params) { + var filtered = ImageFilters.Enrich(data); + + return filtered; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var img = ctx.getImageData(0, 0, canvas_thumb.width, canvas_thumb.height); + var params = {} + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + } + +} + +export default Effects_enrich_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/grains.js b/frontend/src/js/modules/effects/grains.js new file mode 100644 index 0000000..2eeba0f --- /dev/null +++ b/frontend/src/js/modules/effects/grains.js @@ -0,0 +1,111 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Helper_class from './../../libs/helpers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_grains_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + } + + grains() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Grains', + preview: true, + effects: true, + params: [ + {name: "level", title: "Level:", value: "30", range: [0, 50]}, + ], + on_change: function (params, canvas_preview, w, h) { + var img = canvas_preview.getImageData(0, 0, w, h); + var data = _this.change(img, params); + canvas_preview.putImageData(data, 0, 0); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(data, params) { + if (params.level == 0) + return data; + var imgData = data.data; + + var H = data.height; + var W = data.width; + + for (var j = 0; j < H; j++) { + for (var i = 0; i < W; i++) { + var x = (i + j * W) * 4; + if (imgData[x + 3] == 0) + continue; //transparent + //increase it's lightness + var delta = this.Helper.getRandomInt(0, params.level); + if (delta == 0) + continue; + + if (imgData[x] - delta < 0) + imgData[x] = -(imgData[x] - delta); + else + imgData[x] = imgData[x] - delta; + if (imgData[x + 1] - delta < 0) + imgData[x + 1] = -(imgData[x + 1] - delta); + else + imgData[x + 1] = imgData[x + 1] - delta; + if (imgData[x + 2] - delta < 0) + imgData[x + 2] = -(imgData[x + 2] - delta); + else + imgData[x + 2] = imgData[x + 2] - delta; + } + } + + return data; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var img = ctx.getImageData(0, 0, canvas_thumb.width, canvas_thumb.height); + var params = { + level: 30, + } + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + } + +} + +export default Effects_grains_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/heatmap.js b/frontend/src/js/modules/effects/heatmap.js new file mode 100644 index 0000000..31d6949 --- /dev/null +++ b/frontend/src/js/modules/effects/heatmap.js @@ -0,0 +1,111 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_heatmap_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + heatmap() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.change(img); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(data) { + var imgData = data.data; + var grey, RGB; + + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + grey = Math.round(0.2126 * imgData[i] + 0.7152 * imgData[i + 1] + 0.0722 * imgData[i + 2]); + RGB = this.color2heat(grey); + imgData[i] = RGB.R; + imgData[i + 1] = RGB.G; + imgData[i + 2] = RGB.B; + } + + return data; + } + + color2heat(value) { + var RGB = {R: 0, G: 0, B: 0}; + value = value / 255; + if (0 <= value && value <= 1 / 8) { + RGB.R = 0; + RGB.G = 0; + RGB.B = 4 * value + .5; // .5 - 1 // b = 1/2 + } + else if (1 / 8 < value && value <= 3 / 8) { + RGB.R = 0; + RGB.G = 4 * value - .5; // 0 - 1 // b = - 1/2 + RGB.B = 1; // small fix + } + else if (3 / 8 < value && value <= 5 / 8) { + RGB.R = 4 * value - 1.5; // 0 - 1 // b = - 3/2 + RGB.G = 1; + RGB.B = -4 * value + 2.5; // 1 - 0 // b = 5/2 + } + else if (5 / 8 < value && value <= 7 / 8) { + RGB.R = 1; + RGB.G = -4 * value + 3.5; // 1 - 0 // b = 7/2 + RGB.B = 0; + } + else if (7 / 8 < value && value <= 1) { + RGB.R = -4 * value + 4.5; // 1 - .5 // b = 9/2 + RGB.G = 0; + RGB.B = 0; + } + else { + // should never happen - value > 1 + RGB.R = .5; + RGB.G = 0; + RGB.B = 0; + } + // scale for hex conversion + RGB.R *= 255; + RGB.G *= 255; + RGB.B *= 255; + + RGB.R = Math.round(RGB.R); + RGB.G = Math.round(RGB.G); + RGB.B = Math.round(RGB.B); + + return RGB; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var img = ctx.getImageData(0, 0, canvas_thumb.width, canvas_thumb.height); + var data = this.change(img); + ctx.putImageData(data, 0, 0); + } + +} + +export default Effects_heatmap_class; diff --git a/frontend/src/js/modules/effects/instagram/1977.js b/frontend/src/js/modules/effects/instagram/1977.js new file mode 100644 index 0000000..7b5e10a --- /dev/null +++ b/frontend/src/js/modules/effects/instagram/1977.js @@ -0,0 +1,72 @@ +import app from '../../../app.js'; +import config from '../../../config.js'; +import Dialog_class from '../../../libs/popup.js'; +import Base_layers_class from '../../../core/base-layers.js'; +import alertify from 'alertifyjs/build/alertify.min.js'; + +class Effects_1977_class { + + constructor() { + this.POP = new Dialog_class(); + //this.Color_matrix = new Color_matrix_class(); + this.Base_layers = new Base_layers_class(); + } + + 1977() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, width, height) { + + //create temp canvas + var canvas2 = document.createElement('canvas'); + var ctx2 = canvas2.getContext("2d"); + canvas2.width = width; + canvas2.height = height; + ctx2.drawImage(canvas, 0, 0); + + //merge + ctx2.globalCompositeOperation = "screen"; + ctx2.fillStyle = 'rgba(243, 106, 188, 0.3)'; + ctx2.fillRect(0, 0, width, height); + ctx2.globalCompositeOperation = "source-over"; + + //apply more effects + ctx2.filter = 'contrast(1.1) brightness(1.1) saturate(1.3)'; + ctx2.drawImage(canvas2, 0, 0); + ctx2.filter = 'none'; + + return canvas2; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var data = this.change(canvas_thumb, canvas_thumb.width, canvas_thumb.height); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_1977_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/instagram/aden.js b/frontend/src/js/modules/effects/instagram/aden.js new file mode 100644 index 0000000..47c7791 --- /dev/null +++ b/frontend/src/js/modules/effects/instagram/aden.js @@ -0,0 +1,74 @@ +import app from '../../../app.js'; +import config from '../../../config.js'; +import Dialog_class from '../../../libs/popup.js'; +import Base_layers_class from '../../../core/base-layers.js'; +import alertify from 'alertifyjs/build/alertify.min.js'; + +class Effects_aden_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + aden() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, width, height) { + + //create temp canvas + var canvas2 = document.createElement('canvas'); + var ctx2 = canvas2.getContext("2d"); + canvas2.width = width; + canvas2.height = height; + var gradient = ctx2.createLinearGradient(0, 0, width, height); + gradient.addColorStop(0, "rgba(66, 10, 14, 0.2)"); + gradient.addColorStop(1, "rgba(66, 10, 14, 0.2)"); + ctx2.fillStyle = gradient; + ctx2.fillRect(0, 0, width, height); + + //merge + ctx2.globalCompositeOperation = "darken"; + ctx2.drawImage(canvas, 0, 0); + ctx2.globalCompositeOperation = "source-over"; + + //apply more effects + ctx2.filter = 'hue-rotate(-20deg) contrast(0.9) saturate(0.85) brightness(1.2)'; + ctx2.drawImage(canvas2, 0, 0); + ctx2.filter = 'none'; + + return canvas2; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var data = this.change(canvas_thumb, canvas_thumb.width, canvas_thumb.height); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_aden_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/instagram/clarendon.js b/frontend/src/js/modules/effects/instagram/clarendon.js new file mode 100644 index 0000000..738391e --- /dev/null +++ b/frontend/src/js/modules/effects/instagram/clarendon.js @@ -0,0 +1,72 @@ +import app from '../../../app.js'; +import config from '../../../config.js'; +import Dialog_class from '../../../libs/popup.js'; +import Base_layers_class from '../../../core/base-layers.js'; +import alertify from 'alertifyjs/build/alertify.min.js'; + +class Effects_clarendon_class { + + constructor() { + this.POP = new Dialog_class(); + //this.Color_matrix = new Color_matrix_class(); + this.Base_layers = new Base_layers_class(); + } + + clarendon() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, width, height) { + + //create temp canvas + var canvas2 = document.createElement('canvas'); + var ctx2 = canvas2.getContext("2d"); + canvas2.width = width; + canvas2.height = height; + ctx2.fillStyle = 'rgba(127, 187, 227, 0.2)'; + ctx2.fillRect(0, 0, width, height); + + //merge + ctx2.globalCompositeOperation = "overlay"; + ctx2.drawImage(canvas, 0, 0); + ctx2.globalCompositeOperation = "source-over"; + + //apply more effects + ctx2.filter = 'contrast(1.2) saturate(1.35)'; + ctx2.drawImage(canvas2, 0, 0); + ctx2.filter = 'none'; + + return canvas2; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var data = this.change(canvas_thumb, canvas_thumb.width, canvas_thumb.height); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_clarendon_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/instagram/gingham.js b/frontend/src/js/modules/effects/instagram/gingham.js new file mode 100644 index 0000000..f1fb0aa --- /dev/null +++ b/frontend/src/js/modules/effects/instagram/gingham.js @@ -0,0 +1,71 @@ +import app from '../../../app.js'; +import config from '../../../config.js'; +import Dialog_class from '../../../libs/popup.js'; +import Base_layers_class from '../../../core/base-layers.js'; +import alertify from 'alertifyjs/build/alertify.min.js'; + +class Effects_gingham_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + gingham() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, width, height) { + + //create temp canvas + var canvas2 = document.createElement('canvas'); + var ctx2 = canvas2.getContext("2d"); + canvas2.width = width; + canvas2.height = height; + ctx2.drawImage(canvas, 0, 0); + + //merge + ctx2.globalCompositeOperation = "soft-light"; + ctx2.fillStyle = '#e6e6fa'; + ctx2.fillRect(0, 0, width, height); + ctx2.globalCompositeOperation = "source-over"; + + //apply more effects + ctx2.filter = 'brightness(1.05) hue-rotate(-10deg)'; + ctx2.drawImage(canvas2, 0, 0); + ctx2.filter = 'none'; + + return canvas2; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var data = this.change(canvas_thumb, canvas_thumb.width, canvas_thumb.height); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_gingham_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/instagram/inkwell.js b/frontend/src/js/modules/effects/instagram/inkwell.js new file mode 100644 index 0000000..50f8bab --- /dev/null +++ b/frontend/src/js/modules/effects/instagram/inkwell.js @@ -0,0 +1,69 @@ +import app from '../../../app.js'; +import config from '../../../config.js'; +import Dialog_class from '../../../libs/popup.js'; +import Base_layers_class from '../../../core/base-layers.js'; +import alertify from 'alertifyjs/build/alertify.min.js'; + +/* +https://github.com/una/CSSgram/blob/master/source/css/toaster.css +https://github.com/vigetlabs/canvas-instagram-filters +*/ +class Effects_inkwell_class { + + constructor() { + this.POP = new Dialog_class(); + //this.Color_matrix = new Color_matrix_class(); + this.Base_layers = new Base_layers_class(); + } + + inkwell() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, width, height) { + + //create temp canvas + var canvas2 = document.createElement('canvas'); + var ctx2 = canvas2.getContext("2d"); + canvas2.width = width; + canvas2.height = height; + + //apply more effects + ctx2.filter = 'sepia(0.3) contrast(1.1) brightness(1.1) grayscale(1)'; + ctx2.drawImage(canvas, 0, 0); + ctx2.filter = 'none'; + + return canvas2; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var data = this.change(canvas_thumb, canvas_thumb.width, canvas_thumb.height); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_inkwell_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/instagram/lofi.js b/frontend/src/js/modules/effects/instagram/lofi.js new file mode 100644 index 0000000..2422dba --- /dev/null +++ b/frontend/src/js/modules/effects/instagram/lofi.js @@ -0,0 +1,75 @@ +import app from '../../../app.js'; +import config from '../../../config.js'; +import Dialog_class from '../../../libs/popup.js'; +import Base_layers_class from '../../../core/base-layers.js'; +import alertify from 'alertifyjs/build/alertify.min.js'; + +class Effects_lofi_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + lofi() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, width, height) { + + //create temp canvas + var canvas2 = document.createElement('canvas'); + var ctx2 = canvas2.getContext("2d"); + canvas2.width = width; + canvas2.height = height; + ctx2.drawImage(canvas, 0, 0); + + //merge + ctx2.globalCompositeOperation = "multiply"; + var min = Math.min(width, height); + var gradient = ctx2.createRadialGradient(width / 2, height / 2, min * 0.7, width / 2, height / 2, min * 1.5); + gradient.addColorStop(0, "rgba(0,0,0,0)"); + gradient.addColorStop(1, "#222222"); + ctx2.fillStyle = gradient; + ctx2.fillRect(0, 0, width, height); + ctx2.globalCompositeOperation = "source-over"; + + //apply more effects + ctx2.filter = 'saturate(1.1) contrast(1.5)'; + ctx2.drawImage(canvas2, 0, 0); + ctx2.filter = 'none'; + + return canvas2; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var data = this.change(canvas_thumb, canvas_thumb.width, canvas_thumb.height); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_lofi_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/instagram/toaster.js b/frontend/src/js/modules/effects/instagram/toaster.js new file mode 100644 index 0000000..95904bb --- /dev/null +++ b/frontend/src/js/modules/effects/instagram/toaster.js @@ -0,0 +1,79 @@ +import app from '../../../app.js'; +import config from '../../../config.js'; +import Dialog_class from '../../../libs/popup.js'; +import Base_layers_class from '../../../core/base-layers.js'; +import alertify from 'alertifyjs/build/alertify.min.js'; + +/* +https://github.com/una/CSSgram/blob/master/source/css/toaster.css +https://github.com/vigetlabs/canvas-instagram-filters +*/ +class Effects_toaster_class { + + constructor() { + this.POP = new Dialog_class(); + //this.Color_matrix = new Color_matrix_class(); + this.Base_layers = new Base_layers_class(); + } + + toaster() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, width, height) { + + //create temp canvas + var canvas2 = document.createElement('canvas'); + var ctx2 = canvas2.getContext("2d"); + canvas2.width = width; + canvas2.height = height; + ctx2.drawImage(canvas, 0, 0); + + //merge + ctx2.globalCompositeOperation = "screen"; + var gradient = ctx2.createRadialGradient(width / 2, height / 2, 0, width / 2, height / 2, width * 0.6); + gradient.addColorStop(0, "#804e0f"); + gradient.addColorStop(1, "#3b003b"); + ctx2.fillStyle = gradient; + ctx2.fillRect(0, 0, width, height); + ctx2.globalCompositeOperation = "source-over"; + + //apply more effects + ctx2.filter = 'contrast(1.5) brightness(0.9)'; + ctx2.drawImage(canvas2, 0, 0); + ctx2.filter = 'none'; + + return canvas2; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var data = this.change(canvas_thumb, canvas_thumb.width, canvas_thumb.height); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_toaster_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/instagram/valencia.js b/frontend/src/js/modules/effects/instagram/valencia.js new file mode 100644 index 0000000..ec3991b --- /dev/null +++ b/frontend/src/js/modules/effects/instagram/valencia.js @@ -0,0 +1,71 @@ +import app from '../../../app.js'; +import config from '../../../config.js'; +import Dialog_class from '../../../libs/popup.js'; +import Base_layers_class from '../../../core/base-layers.js'; +import alertify from 'alertifyjs/build/alertify.min.js'; + +class Effects_valencia_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + valencia() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, width, height) { + + //create temp canvas + var canvas2 = document.createElement('canvas'); + var ctx2 = canvas2.getContext("2d"); + canvas2.width = width; + canvas2.height = height; + ctx2.drawImage(canvas, 0, 0); + + //merge + ctx2.globalCompositeOperation = "exclusion"; + ctx2.fillStyle = '3a0339'; + ctx2.fillRect(0, 0, width, height); + ctx2.globalCompositeOperation = "source-over"; + + //apply more effects + ctx2.filter = 'contrast(1.08) brightness(1.08) sepia(0.08)'; + ctx2.drawImage(canvas2, 0, 0); + ctx2.filter = 'none'; + + return canvas2; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var data = this.change(canvas_thumb, canvas_thumb.width, canvas_thumb.height); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_valencia_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/instagram/xpro2.js b/frontend/src/js/modules/effects/instagram/xpro2.js new file mode 100644 index 0000000..8e8eb40 --- /dev/null +++ b/frontend/src/js/modules/effects/instagram/xpro2.js @@ -0,0 +1,75 @@ +import app from '../../../app.js'; +import config from '../../../config.js'; +import Dialog_class from '../../../libs/popup.js'; +import Base_layers_class from '../../../core/base-layers.js'; +import alertify from 'alertifyjs/build/alertify.min.js'; + +class Effects_xpro2_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + xpro2() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, width, height) { + + //create temp canvas + var canvas2 = document.createElement('canvas'); + var ctx2 = canvas2.getContext("2d"); + canvas2.width = width; + canvas2.height = height; + ctx2.drawImage(canvas, 0, 0); + + //merge + ctx2.globalCompositeOperation = "color-burn"; + var min = Math.min(width, height); + var gradient = ctx2.createRadialGradient(width / 2, height / 2, min * 0.4, width / 2, height / 2, min * 1.1); + gradient.addColorStop(0, "#e6e7e0"); + gradient.addColorStop(1, "rgba(43, 42, 161, 0.6)"); + ctx2.fillStyle = gradient; + ctx2.fillRect(0, 0, width, height); + ctx2.globalCompositeOperation = "source-over"; + + //apply more effects + ctx2.filter = 'sepia(0.3)'; + ctx2.drawImage(canvas2, 0, 0); + ctx2.filter = 'none'; + + return canvas2; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var data = this.change(canvas_thumb, canvas_thumb.width, canvas_thumb.height); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_xpro2_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/mosaic.js b/frontend/src/js/modules/effects/mosaic.js new file mode 100644 index 0000000..08841f5 --- /dev/null +++ b/frontend/src/js/modules/effects/mosaic.js @@ -0,0 +1,86 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import ImageFilters from './../../libs/imagefilters.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_mosaic_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + mosaic() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Mosaic', + preview: true, + effects: true, + params: [ + {name: "size", title: "Size:", value: 10, range: [1, 100]}, + ], + on_change: function (params, canvas_preview, w, h) { + var img = canvas_preview.getImageData(0, 0, w, h); + var data = _this.change(img, params); + canvas_preview.putImageData(data, 0, 0); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(data, params) { + var size = parseFloat(params.size); + + //convert % to px + size = Math.min(data.width, data.height) * size / 100; + size = Math.round(size); + + var filtered = ImageFilters.Mosaic(data, size); + + return filtered; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var img = ctx.getImageData(0, 0, canvas_thumb.width, canvas_thumb.height); + var params = { + size: 10, + } + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + } + +} + +export default Effects_mosaic_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/night_vision.js b/frontend/src/js/modules/effects/night_vision.js new file mode 100644 index 0000000..dfe29ef --- /dev/null +++ b/frontend/src/js/modules/effects/night_vision.js @@ -0,0 +1,82 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import glfx from './../../libs/glfx.js'; +import ImageFilters_class from './../../libs/imagefilters.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_nightVision_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.fx_filter = false; + this.ImageFilters = ImageFilters_class; + } + + night_vision() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, width, height) { + if (this.fx_filter == false) { + //init glfx lib + this.fx_filter = glfx.canvas(); + } + + //create second copy + var canvas2 = document.createElement('canvas'); + var ctx2 = canvas2.getContext("2d"); + canvas2.width = width; + canvas2.height = height; + ctx2.drawImage(canvas, 0, 0); + + // green overlay + var img = ctx2.getImageData(0, 0, width, height); + //RGB corrections + var img = this.ImageFilters.ColorTransformFilter(img, 1, 1, 1, 1, 0, 100, 0, 1); + //hue/saturation/luminance + var img = this.ImageFilters.HSLAdjustment(img, 0, 0, -50); + ctx2.putImageData(img, 0, 0); + + //vignete + var texture = this.fx_filter.texture(canvas2); + this.fx_filter.draw(texture).vignette(0.2, 0.9).update(); //effect + canvas2 = this.fx_filter; + + return canvas2; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var params = {}; + var data = this.change(canvas_thumb, canvas_thumb.width, canvas_thumb.height); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_nightVision_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/oil.js b/frontend/src/js/modules/effects/oil.js new file mode 100644 index 0000000..7c52056 --- /dev/null +++ b/frontend/src/js/modules/effects/oil.js @@ -0,0 +1,85 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import ImageFilters from './../../libs/imagefilters.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_oil_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + oil() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Oil', + preview: true, + effects: true, + params: [ + {name: "param1", title: "Range:", value: 2, range: [1, 10]}, + {name: "param2", title: "Levels:", value: "32", range: [1, 256]}, + ], + on_change: function (params, canvas_preview, w, h) { + var img = canvas_preview.getImageData(0, 0, w, h); + var data = _this.change(img, params); + canvas_preview.putImageData(data, 0, 0); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(data, params) { + var param1 = parseFloat(params.param1); + var param2 = parseInt(params.param2); + + var filtered = ImageFilters.Oil(data, param1, param2); + + return filtered; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var img = ctx.getImageData(0, 0, canvas_thumb.width, canvas_thumb.height); + var params = { + param1: 2, + param2: 32, + } + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + } + +} + +export default Effects_oil_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/pencil.js b/frontend/src/js/modules/effects/pencil.js new file mode 100644 index 0000000..9837cea --- /dev/null +++ b/frontend/src/js/modules/effects/pencil.js @@ -0,0 +1,73 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_pencil_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + pencil() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, width, height) { + var offset = Math.min(width, height) / 1000; + offset = Math.ceil(offset); + + //create second copy + var canvas2 = document.createElement('canvas'); + var ctx2 = canvas2.getContext("2d"); + canvas2.width = width; + canvas2.height = height; + ctx2.drawImage(canvas, -offset, -offset); + + //merge + ctx2.globalCompositeOperation = "difference"; + ctx2.drawImage(canvas, 0, 0); + ctx2.globalCompositeOperation = "source-over"; + + //apply more effects + ctx2.filter = 'brightness(2) invert(1) grayscale(1)'; + ctx2.drawImage(canvas2, 0, 0); + ctx2.filter = 'none'; + + return canvas2; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var params = {}; + var data = this.change(canvas_thumb, canvas_thumb.width, canvas_thumb.height); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_pencil_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/sharpen.js b/frontend/src/js/modules/effects/sharpen.js new file mode 100644 index 0000000..d699e8a --- /dev/null +++ b/frontend/src/js/modules/effects/sharpen.js @@ -0,0 +1,82 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import ImageFilters from './../../libs/imagefilters.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_sharpen_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + sharpen() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Sharpen', + preview: true, + effects: true, + params: [ + {name: "param1", title: "Factor:", value: "3", range: [1, 10], step: 0.1}, + ], + on_change: function (params, canvas_preview, w, h) { + var img = canvas_preview.getImageData(0, 0, w, h); + var data = _this.change(img, params); + canvas_preview.putImageData(data, 0, 0); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(data, params) { + var param1 = parseFloat(params.param1); + + var filtered = ImageFilters.Sharpen(data, param1); + + return filtered; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var img = ctx.getImageData(0, 0, canvas_thumb.width, canvas_thumb.height); + var params = { + param1: 3, + } + var data = this.change(img, params); + ctx.putImageData(data, 0, 0); + } + +} + +export default Effects_sharpen_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/solarize.js b/frontend/src/js/modules/effects/solarize.js new file mode 100644 index 0000000..fef96f7 --- /dev/null +++ b/frontend/src/js/modules/effects/solarize.js @@ -0,0 +1,55 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import ImageFilters from './../../libs/imagefilters.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_solarize_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + solarize() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.change(img); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(data) { + var filtered = ImageFilters.Solarize(data); + + return filtered; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var img = ctx.getImageData(0, 0, canvas_thumb.width, canvas_thumb.height); + var data = this.change(img); + ctx.putImageData(data, 0, 0); + } + +} + +export default Effects_solarize_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/tilt_shift.js b/frontend/src/js/modules/effects/tilt_shift.js new file mode 100644 index 0000000..4fa8826 --- /dev/null +++ b/frontend/src/js/modules/effects/tilt_shift.js @@ -0,0 +1,144 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import ImageFilters from './../../libs/imagefilters.js'; +import glfx from './../../libs/glfx.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_tiltShift_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.fx_filter = false; + } + + tilt_shift() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Tilt Shift', + preview: true, + effects: true, + params: [ + //extra + {name: "param7", title: "Saturation:", value: "3", range: [0, 20]}, + {name: "param8", title: "Sharpen:", value: "1", range: [0, 5]}, + //main + {name: "param1", title: "Blur Radius:", value: 10, range: [0, 30]}, + {name: "param2", title: "Gradient Radius:", value: 70, range: [40, 100]}, + //startX, startY, endX, endY + {name: "param3", title: "X start:", value: 0, range: [0, 100]}, + {name: "param4", title: "Y start:", value: 50, range: [0, 100]}, + {name: "param5", title: "X end:", value: 100, range: [0, 100]}, + {name: "param6", title: "Y end:", value: 50, range: [0, 100]}, + ], + on_change: function (params, canvas_preview, w, h, canvas_) { + //recalc param by size + _this.change(canvas_, params); + + //convert % to px for line + params.param3 = canvas_.width * params.param3 / 100; + params.param4 = canvas_.height * params.param4 / 100; + params.param5 = canvas_.width * params.param5 / 100; + params.param6 = canvas_.height * params.param6 / 100; + + //draw line + canvas_preview.beginPath(); + canvas_preview.strokeStyle = "#ff0000"; + canvas_preview.lineWidth = 1; + canvas_preview.moveTo(params.param3 + 0.5, params.param4 + 0.5); + canvas_preview.lineTo(params.param5 + 0.5, params.param6 + 0.5); + canvas_preview.stroke(); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + this.change(canvas, params); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, params) { + if (this.fx_filter == false) { + //init glfx lib + this.fx_filter = glfx.canvas(); + } + + var param1 = parseInt(params.param1); + var param2 = parseInt(params.param2); + var param3 = parseInt(params.param3); + var param4 = parseInt(params.param4); + var param5 = parseInt(params.param5); + var param6 = parseInt(params.param6); + var param7 = parseInt(params.param7); + var param8 = parseInt(params.param8); + + //convert % to px + param1 = canvas.height * param1 / 100; + param2 = canvas.height * param2 / 100; + param3 = canvas.width * param3 / 100; + param4 = canvas.height * param4 / 100; + param5 = canvas.width * param5 / 100; + param6 = canvas.height * param6 / 100; + + var ctx = canvas.getContext("2d"); + + //main effect + var texture = this.fx_filter.texture(canvas); + this.fx_filter.draw(texture).tiltShift(param3, param4, param5, param6, param1, param2).update(); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(this.fx_filter, 0, 0); + + //saturation + var data = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = ImageFilters.HSLAdjustment(data, 0, param7, 0); + ctx.putImageData(data, 0, 0); + + //sharpen + var data = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = ImageFilters.Sharpen(data, param8); + ctx.putImageData(data, 0, 0); + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var params = { + param7: 3, + param8: 1, + param1: 10, + param2: 70, + param3: 0, + param4: 50, + param5: 100, + param6: 50, + } + var data = this.change(canvas, params); + } + +} + +export default Effects_tiltShift_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/vibrance.js b/frontend/src/js/modules/effects/vibrance.js new file mode 100644 index 0000000..56c31aa --- /dev/null +++ b/frontend/src/js/modules/effects/vibrance.js @@ -0,0 +1,89 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import glfx from './../../libs/glfx.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_vibrance_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.fx_filter = false; + } + + vibrance() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Vibrance', + preview: true, + effects: true, + params: [ + {name: "level", title: "Level:", value: "0.5", range: [-1, 1], step: 0.01}, + ], + on_change: function (params, canvas_preview, w, h, canvas_) { + var data = _this.change(canvas_, params); + canvas_preview.clearRect(0, 0, canvas_.width, canvas_.height); + canvas_preview.drawImage(data, 0, 0); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, params); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, params) { + if (this.fx_filter == false) { + //init glfx lib + this.fx_filter = glfx.canvas(); + } + + var param1 = parseFloat(params.level); + + var texture = this.fx_filter.texture(canvas); + this.fx_filter.draw(texture).vibrance(param1).update(); //effect + + return this.fx_filter; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var params = { + level: 0.5, + }; + var data = this.change(canvas_thumb, params); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_vibrance_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/vignette.js b/frontend/src/js/modules/effects/vignette.js new file mode 100644 index 0000000..e694ae2 --- /dev/null +++ b/frontend/src/js/modules/effects/vignette.js @@ -0,0 +1,92 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import glfx from './../../libs/glfx.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_vignette_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.fx_filter = false; + } + + vignette() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Vignette', + preview: true, + effects: true, + params: [ + {name: "param1", title: "Level:", value: "0.5", range: [0, 1], step: 0.01}, + {name: "param2", title: "Size:", value: "0.5", range: [0, 1], step: 0.01}, + ], + on_change: function (params, canvas_preview, w, h, canvas_) { + var data = _this.change(canvas_, params); + canvas_preview.clearRect(0, 0, canvas_.width, canvas_.height); + canvas_preview.drawImage(data, 0, 0); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, params); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, params) { + if (this.fx_filter == false) { + //init glfx lib + this.fx_filter = glfx.canvas(); + } + + var param1 = parseFloat(params.param1); + var param2 = parseFloat(params.param2); + + var texture = this.fx_filter.texture(canvas); + this.fx_filter.draw(texture).vignette(param1, param2).update(); //effect + + return this.fx_filter; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var params = { + param1: 0.5, + param2: 0.5, + }; + var data = this.change(canvas_thumb, params); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_vignette_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/vintage.js b/frontend/src/js/modules/effects/vintage.js new file mode 100644 index 0000000..da8ab2a --- /dev/null +++ b/frontend/src/js/modules/effects/vintage.js @@ -0,0 +1,77 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Vintage_class from './../../libs/vintage.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_vintage_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Vintage = new Vintage_class(config.WIDTH, config.HEIGHT); + } + + vintage() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + this.Vintage.reset_random_values(config.WIDTH, config.HEIGHT); + + var settings = { + title: 'Vintage', + preview: true, + effects: true, + params: [ + {name: "level", title: "Level:", value: 50, range: [0, 100]}, + ], + on_change: function (params, canvas_preview, w, h, canvas_) { + _this.change(canvas_, params); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + this.change(canvas, params); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, params) { + var level = parseInt(params.level); + + this.Vintage.apply_all(canvas, level); + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + ctx.drawImage(canvas_thumb, 0, 0); + + //now update + var params = { + level: 50, + }; + this.change(canvas, params); + } + +} + +export default Effects_vintage_class; \ No newline at end of file diff --git a/frontend/src/js/modules/effects/zoom_blur.js b/frontend/src/js/modules/effects/zoom_blur.js new file mode 100644 index 0000000..3e1459f --- /dev/null +++ b/frontend/src/js/modules/effects/zoom_blur.js @@ -0,0 +1,102 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import glfx from './../../libs/glfx.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Effects_zoomBlur_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.fx_filter = false; + } + + zoom_blur() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get layer size + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + + var settings = { + title: 'Zoom blur', + preview: true, + effects: true, + params: [ + {name: "param1", title: "Strength:", value: "0.3", range: [0, 1], step: 0.01}, + {name: "param2", title: "Center x:", value: Math.round(canvas.width / 2), range: [0, canvas.width]}, + {name: "param3", title: "Center y:", value: Math.round(canvas.height / 2), range: [0, canvas.height]}, + ], + on_change: function (params, canvas_preview, w, h, canvas_) { + //recalc param by size + params.param2 = params.param2 / canvas.width * w; + params.param3 = params.param3 / canvas.height * h; + + var data = _this.change(canvas_, params); + canvas_preview.clearRect(0, 0, canvas_.width, canvas_.height); + canvas_preview.drawImage(data, 0, 0); + }, + on_finish: function (params) { + _this.save(params); + }, + }; + this.POP.show(settings); + } + + save(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var data = this.change(canvas, params); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(canvas, params) { + if (this.fx_filter == false) { + //init glfx lib + this.fx_filter = glfx.canvas(); + } + + var param1 = parseFloat(params.param1); + var param2 = parseInt(params.param2); + var param3 = parseInt(params.param3); + + var texture = this.fx_filter.texture(canvas); + this.fx_filter.draw(texture).zoomBlur(param2, param3, param1).update(); //effect + + return this.fx_filter; + } + + demo(canvas_id, canvas_thumb){ + var canvas = document.getElementById(canvas_id); + var ctx = canvas.getContext("2d"); + + //modify + var params = { + param1: 0.3, + param2: Math.round(canvas_thumb.width / 2), + param3: Math.round(canvas_thumb.height / 2), + }; + var data = this.change(canvas_thumb, params); + + //draw + ctx.drawImage(data, 0, 0); + } + +} + +export default Effects_zoomBlur_class; \ No newline at end of file diff --git a/frontend/src/js/modules/file/new.js b/frontend/src/js/modules/file/new.js new file mode 100644 index 0000000..bca419f --- /dev/null +++ b/frontend/src/js/modules/file/new.js @@ -0,0 +1,137 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_gui_class from './../../core/base-gui.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Helper_class from './../../libs/helpers.js'; +import Dialog_class from './../../libs/popup.js'; +import Tools_settings_class from './../tools/settings.js'; + +/** + * manages files / new + * + * @author ViliusL + */ +class File_new_class { + + constructor() { + this.Base_gui = new Base_gui_class(); + this.Base_layers = new Base_layers_class(); + this.POP = new Dialog_class(); + this.Helper = new Helper_class(); + this.Tools_settings = new Tools_settings_class(); + } + + new () { + var _this = this; + var width = config.WIDTH; + var height = config.HEIGHT; + var common_dimensions = this.Base_gui.common_dimensions; + var resolution_types = ['Custom']; + var units = this.Tools_settings.get_setting('default_units'); + var resolution = this.Tools_settings.get_setting('resolution'); + + for (var i in common_dimensions) { + var value = common_dimensions[i]; + resolution_types.push(value[0] + 'x' + value[1] + ' - ' + value[2]); + } + + var transparency_cookie = this.Helper.getCookie('transparency'); + if (transparency_cookie === null) { + //default + transparency_cookie = false; + } + if (transparency_cookie) { + var transparency = true; + } + else { + var transparency = false; + } + + //convert units + width = this.Helper.get_user_unit(width, units, resolution); + height = this.Helper.get_user_unit(height, units, resolution); + + var settings = { + title: 'New file', + params: [ + {name: "width", title: "Width:", value: width, comment: units}, + {name: "height", title: "Height:", value: height, comment: units}, + {name: "resolution_type", title: "Resolution:", values: resolution_types}, + {name: "layout", title: "Layout:", value: "Custom", values: ["Custom", "Landscape", "Portrait"]}, + {name: "transparency", title: "Transparent:", value: transparency}, + ], + on_finish: function (params) { + _this.new_handler(params); + }, + }; + this.POP.show(settings); + } + + async new_handler(response) { + var width = parseFloat(response.width); + var height = parseFloat(response.height); + var resolution_type = response.resolution_type; + var transparency = response.transparency; + var units = this.Tools_settings.get_setting('default_units'); + var resolution = this.Tools_settings.get_setting('resolution'); + + if (resolution_type != 'Custom') { + var dim = resolution_type.split(" "); + dim = dim[0].split("x"); + width = parseInt(dim[0]); + height = parseInt(dim[1]); + + if(response.layout == 'Portrait'){ + var tmp = width; + width = height; + height = tmp; + } + } + else { + //convert units + width = this.Helper.get_internal_unit(width, units, resolution); + height = this.Helper.get_internal_unit(height, units, resolution); + } + + // Prepare layers + app.State.do_action( + new app.Actions.Bundle_action('new_file', 'New File', [ + new app.Actions.Refresh_action_attributes_action('undo'), + new app.Actions.Prepare_canvas_action('undo'), + new app.Actions.Update_config_action({ + TRANSPARENCY: !!transparency, + WIDTH: parseInt(width), + HEIGHT: parseInt(height), + ALPHA: 255, + COLOR: '#008000', + mouse: {}, + visible_width: null, + visible_height: null, + user_fonts: {} + }), + new app.Actions.Prepare_canvas_action('do'), + new app.Actions.Refresh_action_attributes_action('do'), + new app.Actions.Reset_layers_action(), + new app.Actions.Init_canvas_zoom_action(), + new app.Actions.Insert_layer_action({}) + ]) + ); + + //sleep, lets wait till DOM is finished + await new Promise(r => setTimeout(r, 10)); + + //fit to screen? + this.Base_gui.GUI_preview.zoom_auto(true); + + // Save transparency + if (transparency) { + this.Helper.setCookie('transparency', 1); + } + else { + this.Helper.setCookie('transparency', 0); + } + } + +} + +export default File_new_class; \ No newline at end of file diff --git a/frontend/src/js/modules/file/open.js b/frontend/src/js/modules/file/open.js new file mode 100644 index 0000000..494992b --- /dev/null +++ b/frontend/src/js/modules/file/open.js @@ -0,0 +1,708 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Base_gui_class from './../../core/base-gui.js'; +import Dialog_class from './../../libs/popup.js'; +import Helper_class from './../../libs/helpers.js'; +import Clipboard_class from './../../libs/clipboard.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; +import EXIF from './../../../../node_modules/exif-js/exif.js'; +import GUI_tools_class from "../../core/gui/gui-tools"; +import semver_compare from './../../../../node_modules/semver-compare/'; + +var instance = null; + +/** + * manages files / open + * + * @author ViliusL + */ +class File_open_class { + + constructor() { + //singleton + if (instance) { + return instance; + } + instance = this; + + var _this = this; + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Base_gui = new Base_gui_class(); + this.Helper = new Helper_class(); + this.GUI_tools = new GUI_tools_class(); + + //clipboard class + this.Clipboard_class = new Clipboard_class(function (data, w, h) { + _this.on_paste(data, w, h); + }); + + this.events(); + + this.maybe_file_open_url_handler(); + } + + events() { + var _this = this; + + window.ondrop = function (e) { + //drop + e.preventDefault(); + _this.open_handler(e); + }; + window.ondragover = function (e) { + e.preventDefault(); + }; + document.addEventListener('keydown', (event) => { + var code = event.key.toLowerCase(); + if (this.Helper.is_input(event.target)) + return; + + if (code == "o") { + //open + this.open_file(); + event.preventDefault(); + } + }, false); + } + + on_paste(data, width, height) { + var new_layer = { + name: 'Paste', + type: 'image', + data: data, + }; + app.State.do_action( + new app.Actions.Insert_layer_action(new_layer) + ); + } + + open_file() { + var _this = this; + + alertify.success('You can also drag and drop items into browser.'); + + document.getElementById("tmp").innerHTML = ''; + var a = document.createElement('input'); + a.setAttribute("id", "file_open"); + a.type = 'file'; + a.multiple = 'multiple'; + document.getElementById("tmp").appendChild(a); + document.getElementById('file_open').addEventListener('change', function (e) { + _this.open_handler(e); + }, false); + + //force click + document.querySelector('#file_open').click(); + } + + open_webcam(){ + var _this = this; + var video = document.createElement('video'); + video.autoplay = true; + video.style.maxWidth = '100%'; + var track = null; + + function handleSuccess(stream) { + track = stream.getTracks()[0]; + video.srcObject = stream; + } + + function handleError(error) { + alertify.error('Sorry, cold not load getUserMedia() data: ' + error); + } + + var settings = { + title: 'Webcam', + params: [ + {title: "Stream:", html: '
'}, + ], + on_load: function(params){ + document.getElementById('webcam_container').appendChild(video); + }, + on_finish: function(params){ + //capture data + var width = video.videoWidth; + var height = video.videoHeight; + + var tmpCanvas = document.createElement('canvas'); + var tmpCanvasCtx = tmpCanvas.getContext("2d"); + tmpCanvas.width = width; + tmpCanvas.height = height; + tmpCanvasCtx.drawImage(video, 0, 0); + + //create requested layer + var new_layer = { + name: "Webcam #" + _this.Base_layers.auto_increment, + type: 'image', + data: tmpCanvas.toDataURL("image/png"), + width: width, + height: height, + width_original: width, + height_original: height, + }; + app.State.do_action( + new app.Actions.Bundle_action('open_file_webcam', 'Open File Webcam', [ + new app.Actions.Insert_layer_action(new_layer), + new app.Actions.Autoresize_canvas_action(width, height, null, true, true) + ]) + ); + + //destroy + if(track != null){ + track.stop(); + } + video.pause(); + video.src = ""; + video.load(); + }, + on_cancel: function(params){ + if(track != null){ + track.stop(); + } + video.pause(); + video.src = ""; + video.load(); + }, + }; + this.POP.show(settings); + + navigator.mediaDevices.getUserMedia({audio: false, video: true}) + .then(handleSuccess) + .catch(handleError); + } + + open_dir() { + var _this = this; + + document.getElementById("tmp").innerHTML = ''; + var a = document.createElement('input'); + a.setAttribute("id", "file_open_dir"); + a.type = 'file'; + a.webkitdirectory = 'webkitdirectory'; + document.getElementById("tmp").appendChild(a); + document.getElementById('file_open_dir').addEventListener('change', function (e) { + _this.open_handler(e); + }, false); + + //force click + document.querySelector('#file_open_dir').click(); + } + + /** + * opens data URLs, like: "data:image/png;base64,xxxxxx" + * + * data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAG0lEQVQYV2N89+7df0FBQQbG/////3///j0DAF9wCsg9spQfAAAAAElFTkSuQmCC + */ + open_data_url() { + var _this = this; + + var settings = { + title: 'Open data URL', + params: [ + {name: "data", title: "Data URL:", type: "textarea", value: ""}, + ], + on_finish: function (params) { + _this.file_open_data_url_handler(params.data); + }, + }; + this.POP.show(settings); + } + + file_open_data_url_handler(data) { + var _this = this; + if (data == '') + return; + + var img = new Image(); + img.crossOrigin = "Anonymous"; + img.onload = function () { + var new_layer = { + name: "Data URL", + type: 'image', + link: img, + width: img.width, + height: img.height, + width_original: img.width, + height_original: img.height, + }; + app.State.do_action( + new app.Actions.Bundle_action('open_file_data_url', 'Open File Data URL', [ + new app.Actions.Insert_layer_action(new_layer), + new app.Actions.Autoresize_canvas_action(img.width, img.height, null, true, true) + ]) + ); + img.onload = function () { + config.need_render = true; + }; + }; + img.onerror = function (ex) { + alertify.error('Sorry, image could not be loaded. Try copy image and paste it.'); + }; + img.src = data; + } + + open_url() { + var _this = this; + + var settings = { + title: 'Open URL', + params: [ + {name: "url", title: "URL:", value: ""}, + ], + on_finish: function (params) { + _this.file_open_url_handler(params); + }, + }; + this.POP.show(settings); + } + + async open_handler(e) { + var _this = this; + var files = e.target.files; + + var auto_increment = this.Base_layers.auto_increment; + + if (files == undefined) { + //drag and drop + files = e.dataTransfer.files; + } + + //sort + var orders = []; + for (var i = 0, f; i < files.length; i++) { + orders.push(files[i].name); + } + orders.sort(); + var order_map = []; + for (var i in orders) { + order_map[orders[i]] = parseInt(i); + } + + //check if dropped directory + var dir_opened = false; + if (e.dataTransfer && e.dataTransfer.items) { + var items = e.dataTransfer.items; + for (var i=0; i setTimeout(r, 10)); + } + + //try to open dropped directory + if (e.dataTransfer && e.dataTransfer.items) { + var items = e.dataTransfer.items; + for (var i=0; i setTimeout(r, 10)); + + }); + } + else if (item.isDirectory) { + // Get folder contents + var dirReader = item.createReader(); + dirReader.readEntries(function(entries) { + for (var i=0; i max_id_order) + max_id_order = value.id; + if(typeof value.order != undefined && value.order > max_id_order) + max_id_order = value.order; + + if (value.type == 'image') { + //add image data + value.link = null; + for (var j in json.data) { + if (json.data[j].id == value.id) { + value.data = json.data[j].data; + } + } + } + actions.push( + new app.Actions.Insert_layer_action(value, false) + ); + } + if (json.info.layer_active != undefined) { + actions.push( + new app.Actions.Select_layer_action(json.info.layer_active, true) + ); + } + if (json.info.guides != undefined) { + config.guides = json.info.guides; + } + actions.push( + new app.Actions.Set_object_property_action(this.Base_layers, 'auto_increment', max_id_order + 1), + new app.Actions.Update_config_action({ + WIDTH: parseInt(json.info.width), + HEIGHT: parseInt(json.info.height), + }), + new app.Actions.Prepare_canvas_action('do') + ); + await app.State.do_action( + new app.Actions.Bundle_action('open_json_file', 'Open JSON File', actions) + ); + } + + /** + * Returns an action that saves the exif data of the provided object to the current layer + */ + extract_exif(object) { + var exif_data = { + general: [], + exif: [], + }; + + //exif data + EXIF.getData(object, function () { + exif_data.exif = this.exifdata; + delete this.exifdata.thumbnail; + }); + + //general + if (object.name != undefined) + exif_data.general.Name = object.name; + if (object.size != undefined) + exif_data.general.Size = this.Helper.number_format(object.size / 1000, 2) + ' KB'; + if (object.type != undefined) + exif_data.general.Type = object.type; + if (object.lastModified != undefined) + exif_data.general['Last modified'] = this.Helper.format_time(object.lastModified); + + return exif_data; + } + + search(){ + this.GUI_tools.activate_tool('media'); + } +} + +export default File_open_class; + diff --git a/frontend/src/js/modules/file/print.js b/frontend/src/js/modules/file/print.js new file mode 100644 index 0000000..dbfc77f --- /dev/null +++ b/frontend/src/js/modules/file/print.js @@ -0,0 +1,14 @@ +/** + * manages files / print + * + * @author ViliusL + */ +class File_print_class { + + print() { + window.print(); + } + +} + +export default File_print_class; \ No newline at end of file diff --git a/frontend/src/js/modules/file/quickload.js b/frontend/src/js/modules/file/quickload.js new file mode 100644 index 0000000..c78fae8 --- /dev/null +++ b/frontend/src/js/modules/file/quickload.js @@ -0,0 +1,46 @@ +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import File_open_class from './open.js'; + +/** + * manages files / quick-load + * + * @author ViliusL + */ +class File_quickload_class { + + constructor() { + this.Base_layers = new Base_layers_class(); + this.File_open = new File_open_class(); + + this.set_events(); + } + + set_events() { + var _this = this; + + document.addEventListener('keydown', function (event) { + var code = event.keyCode; + + if (code == 121) { + //F10 + _this.quickload(); + event.preventDefault(); + } + }, false); + } + + quickload() { + //load image data + var json = localStorage.getItem('quicksave_data'); + if (json == '' || json == null) { + //nothing was found + return false; + } + + this.File_open.load_json(json); + } + +} + +export default File_quickload_class; \ No newline at end of file diff --git a/frontend/src/js/modules/file/quicksave.js b/frontend/src/js/modules/file/quicksave.js new file mode 100644 index 0000000..729c014 --- /dev/null +++ b/frontend/src/js/modules/file/quicksave.js @@ -0,0 +1,45 @@ +import config from './../../config.js'; +import File_save_class from './save.js'; +import Dialog_class from './../../libs/popup.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +/** + * manages files / quick-save + * + * @author ViliusL + */ +class File_quicksave_class { + + constructor() { + this.POP = new Dialog_class(); + this.File_save = new File_save_class(); + + this.set_events(); + } + + set_events() { + var _this = this; + + document.addEventListener('keydown', function (event) { + var code = event.keyCode; + + if (code == 120) { + //F9 + _this.quicksave(); + } + }, false); + } + + quicksave() { + //save image data + var data_json = this.File_save.export_as_json(); + if (data_json.length > 5000000) { + alertify.error('Sorry, image is too big, max 5 MB.'); + return false; + } + localStorage.setItem('quicksave_data', data_json); + } + +} + +export default File_quicksave_class; \ No newline at end of file diff --git a/frontend/src/js/modules/file/save.js b/frontend/src/js/modules/file/save.js new file mode 100644 index 0000000..e1b1588 --- /dev/null +++ b/frontend/src/js/modules/file/save.js @@ -0,0 +1,736 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Helper_class from './../../libs/helpers.js'; +import Dialog_class from './../../libs/popup.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; +import canvasToBlob from './../../../../node_modules/blueimp-canvas-to-blob/js/canvas-to-blob.min.js'; +import filesaver from './../../../../node_modules/file-saver/dist/FileSaver.min.js'; +import GIF from './../../../../node_modules/gif.js.optimized/'; +import CanvasToTIFF from './../../libs/canvastotiff.js'; +import Tools_settings_class from "../tools/settings"; + +var instance = null; + +/** + * manages files / save + * + * @author ViliusL + */ +class File_save_class { + + constructor() { + //singleton + if (instance) { + return instance; + } + instance = this; + + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.POP = new Dialog_class(); + this.Tools_settings = new Tools_settings_class(); + + this.set_events(); + + //save types config + this.SAVE_TYPES = { + PNG: "Portable Network Graphics", + JPG: "JPG/JPEG Format", + //AVIF: "AV1 Image File Format", //just uncomment it in future to make it work + JSON: "Full layers data", + WEBP: "Weppy File Format", + GIF: "Graphics Interchange Format", + BMP: "Windows Bitmap", + TIFF: "Tag Image File Format", + }; + + this.default_extension = 'PNG'; + } + + set_events() { + document.addEventListener('keydown', (event) => { + var code = event.key.toLowerCase(); + if (this.Helper.is_input(event.target)) + return; + + if (code == "s") { + if(event.shiftKey){ + //export + this.save(); + } + else{ + //save + this.export(); + } + event.preventDefault(); + } + }, false); + } + + /** + * saves as non destructive mode (including layers, RAW) + */ + save(){ + var types = JSON.parse(JSON.stringify(this.SAVE_TYPES)); + for(var i in types){ + if(i != 'JSON'){ + delete types[i]; + } + } + + this.save_general(types, 'Save as'); + + } + + /** + * save as encoded image + */ + export(){ + var types = JSON.parse(JSON.stringify(this.SAVE_TYPES)); + delete types.JSON; + + this.save_general(types, 'Export'); + } + + save_general(file_types, title) { + var _this = this; + + //find default format + var save_default = null; + var save_default_cookie = this.Helper.getCookie('save_default'); + + for(var i in file_types) { + if(save_default_cookie == i){ + save_default = i; + break; + } + } + if(save_default == null){ + save_default = Object.keys(file_types)[0]; + } + save_default = save_default + " - " + file_types[save_default]; + + var calc_size_value = false; + var calc_size = false; + if (config.WIDTH * config.HEIGHT < 1000000) { + calc_size_value = true; + calc_size = true; + } + + var file_name = config.layers[0].name; + var parts = file_name.split('.'); + if (parts.length > 1) + file_name = parts[parts.length - 2]; + file_name = file_name.replace(/ /g, "-"); + file_name = this.Helper.escapeHtml(file_name); + + var save_types = []; + for(var i in file_types) { + save_types.push(i + " - " + file_types[i]); + } + + var save_layers_types = [ + 'All', + 'Selected', + 'Separated', + 'Separated (original types)', + ]; + var resolution = this.Tools_settings.get_setting('resolution'); + + var settings = { + title: title, + params: [ + {name: "name", title: "File name:", value: file_name}, + {name: "type", title: "Save as type:", values: save_types, value: save_default}, + {name: "quality", title: "Quality:", value: 90, range: [1, 100]}, + {title: "File size:", html: '-'}, + {title: "Resolution:", value: resolution}, + {name: "calc_size", title: "Show file size:", value: calc_size_value}, + {name: "layers", title: "Save layers:", values: save_layers_types}, + {name: "delay", title: "Gif delay:", value: 400}, + ], + on_change: function (params, canvas_preview, w, h) { + _this.save_dialog_onchange(true); + }, + on_finish: function (params) { + if (params.layers == 'Separated' || params.layers == 'Separated (original types)') { + var active_layer = config.layer.id; + var original_layer_type = params.layers; + + //alter params + params.layers = 'Selected'; + + for (var i in config.layers) { + if (config.layers[i].visible == false) + continue; + + //detect type + if (original_layer_type == 'Separated (original types)') { + //detect type from file name + params.type = _this.SAVE_TYPES[_this.default_extension]; + for (var j in _this.SAVE_TYPES) { + if (_this.Helper.strpos(config.layers[i].name.toLowerCase(), '.' + j.toLowerCase()) !== false) { + params.type = j; + break; + } + } + } + + new app.Actions.Select_layer_action(config.layers[i].id, true).do(); + _this.save_action(params, true); + } + new app.Actions.Select_layer_action(active_layer, true).do(); + } + else { + _this.save_action(params); + } + }, + }; + this.POP.show(settings); + + document.getElementById("pop_data_name").select(); + + if (calc_size == true) { + //calc size once + this.save_dialog_onchange(true); + } + else{ + this.save_dialog_onchange(false); + } + } + + save_data_url() { + var max = 10 * 1000 * 1000; + if (config.WIDTH * config.WIDTH > 10 * 1000 * 1000) { + alertify.error('Size is too big, max ' + this.Helper.number_format(max, 0) + ' pixels.'); + return; + } + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext("2d"); + canvas.width = config.WIDTH; + canvas.height = config.HEIGHT; + + this.disable_canvas_smooth(ctx); + + //ask data + this.Base_layers.convert_layers_to_canvas(ctx, null, false); + var data_url = canvas.toDataURL(); + + max = 1000 * 1000; + if (data_url.length > max) { + alertify.error('Size is too big, max ' + this.Helper.number_format(max, 0) + ' bytes.'); + return; + } + + var settings = { + title: 'Data URL', + params: [ + {name: "url", title: "URL:", type: "textarea", value: data_url}, + ], + }; + this.POP.show(settings); + } + + update_file_size(file_size) { + if (typeof file_size == 'string') { + document.getElementById('file_size').innerHTML = file_size; + return; + } + + if (file_size > 1024 * 1024) + file_size = this.Helper.number_format(file_size / 1024 / 1024, 2) + ' MB'; + else if (file_size > 1024) + file_size = this.Helper.number_format(file_size / 1024, 2) + ' KB'; + else + file_size = (file_size) + ' B'; + document.getElementById('file_size').innerHTML = file_size; + } + + /** + * /activated on save dialog parameters change - used for calculating file size + * + * @param {boolean} calculate_file_size + */ + save_dialog_onchange(calculate_file_size) { + var _this = this; + var user_response = this.POP.get_params(); + + var quality = parseInt(user_response.quality); + if (quality > 100 || quality < 1 || isNaN(quality) == true) + quality = 90; + quality = quality / 100; + + //detect type + var type = user_response.type; + var parts = type.split(" "); + type = parts[0]; + + if (type == 'JPG' || type == 'WEBP') + document.getElementById('popup-tr-quality').style.display = ''; + else + document.getElementById('popup-tr-quality').style.display = 'none'; + + if (type == 'GIF') + document.getElementById('popup-tr-delay').style.display = ''; + else + document.getElementById('popup-tr-delay').style.display = 'none'; + + if (type == 'JSON' || type == 'GIF') + document.getElementById('popup-tr-layers').style.display = 'none'; + else + document.getElementById('popup-tr-layers').style.display = ''; + + if (user_response.layers == 'Separated') + document.getElementById('pop_data_name').disabled = true; + else + document.getElementById('pop_data_name').disabled = false; + + if (user_response.layers == 'Separated (original types)') { + if(document.getElementById('popup-group-type')) { + document.getElementById('popup-group-type').style.opacity = "0.5"; + } + document.getElementById('popup-tr-quality').style.display = ''; + } + else { + if(document.getElementById('popup-group-type')) { + document.getElementById('popup-group-type').style.opacity = "1"; + } + } + + if(calculate_file_size == false){ + return; + } + + this.update_file_size('...'); + + if (user_response.calc_size == false || user_response.layers == 'Separated' + || user_response.layers == 'Separated (original types)') { + + document.getElementById('file_size').innerHTML = '-'; + return; + } + + if (type != 'JSON') { + //create temp canvas + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext("2d"); + canvas.width = config.WIDTH; + canvas.height = config.HEIGHT; + this.disable_canvas_smooth(ctx); + + //ask data + if (user_response.layers == 'Selected' && type != 'GIF' && config.layer.type != null) { + //only current layer !!! + var layer = config.layer; + + var initial_x = null; + var initial_y = null; + if (layer.x != null && layer.y != null && layer.width != null && layer.height != null) { + //change position to top left corner + initial_x = layer.x; + initial_y = layer.y; + layer.x = 0; + layer.y = 0; + + canvas.width = layer.width; + canvas.height = layer.height; + } + + this.Base_layers.convert_layers_to_canvas(ctx, layer.id, false); + + if (initial_x != null) { + //restore position + layer.x = initial_x; + layer.y = initial_y; + } + } + else { + this.Base_layers.convert_layers_to_canvas(ctx, null, false); + } + } + + if (type != 'JSON' && (type == 'JPG' || config.TRANSPARENCY == false)) { + //add white background + ctx.globalCompositeOperation = 'destination-over'; + this.fillCanvasBackground(ctx, '#ffffff'); + ctx.globalCompositeOperation = 'source-over'; + } + + //calc size + if (type == 'PNG') { + //png + canvas.toBlob(function (blob) { + _this.update_file_size(blob.size); + }); + } + else if (type == 'JPG') { + //jpg + canvas.toBlob(function (blob) { + _this.update_file_size(blob.size); + }, "image/jpeg", quality); + } + else if (type == 'WEBP') { + //WEBP + var data_header = "image/webp"; + + //check support + if (this.check_format_support(canvas, data_header, false) == false) { + this.update_file_size('-'); + return; + } + + canvas.toBlob(function (blob) { + _this.update_file_size(blob.size); + }, data_header, quality); + } + else if (type == 'AVIF') { + //AVIF + var data_header = "image/avif"; + + //check support + if (this.check_format_support(canvas, data_header, false) == false) { + this.update_file_size('-'); + return; + } + + canvas.toBlob(function (blob) { + _this.update_file_size(blob.size); + }, data_header, quality); + } + else if (type == 'BMP') { + //bmp + var data_header = "image/bmp"; + + //check support + if (this.check_format_support(canvas, data_header, false) == false) { + this.update_file_size('-'); + return; + } + + canvas.toBlob(function (blob) { + _this.update_file_size(blob.size); + }, data_header); + } + else if (type == 'TIFF') { + //tiff + var data_header = "image/tiff"; + + CanvasToTIFF.toBlob(canvas, function(blob) { + _this.update_file_size(blob.size); + }, data_header); + } + else if (type == 'JSON') { + //json + var data_json = this.export_as_json(); + + var blob = new Blob([data_json], {type: "text/plain"}); + this.update_file_size(blob.size); + } + else if (type == 'GIF') { + //gif + this.update_file_size('-'); + } + } + + /** + * saves data in requested way + * + * @param {object} user_response parameters + * @param {boolean} autoname if use name from layer, false by default + */ + save_action(user_response, autoname) { + var fname = user_response.name; + if(autoname === true && user_response.layers == 'Selected'){ + fname = config.layer.name; + } + + var quality = parseInt(user_response.quality); + if (quality > 100 || quality < 1 || isNaN(quality) == true) + quality = 90; + quality = quality / 100; + + var delay = parseInt(user_response.delay); + if (delay < 0 || isNaN(delay) == true) + delay = 400; + + //detect type + var type = user_response.type; + var parts = type.split(" "); + type = parts[0]; + + //detect type from file name + for(var i in this.SAVE_TYPES) { + if (this.Helper.strpos(fname, '.' + i.toLowerCase()) !== false) { + type = i; + } + } + + //save default type as cookie + if(this.Helper.getCookie('save_default') == '' || this.Helper.getCookie('save_default') != type){ + this.Helper.setCookie('save_default', type); + } + + if (type != 'JSON') { + //temp canvas + var canvas; + var ctx; + + //get data + if (user_response.layers == 'Selected' && type != 'GIF') { + canvas = this.Base_layers.convert_layer_to_canvas(); + ctx = canvas.getContext("2d"); + } + else { + canvas = document.createElement('canvas'); + ctx = canvas.getContext("2d"); + canvas.width = config.WIDTH; + canvas.height = config.HEIGHT; + this.disable_canvas_smooth(ctx); + + this.Base_layers.convert_layers_to_canvas(ctx, null, false); + } + } + + if (type != 'JSON' && (type == 'JPG' || config.TRANSPARENCY == false)) { + //add white background + ctx.globalCompositeOperation = 'destination-over'; + this.fillCanvasBackground(ctx, '#ffffff'); + ctx.globalCompositeOperation = 'source-over'; + } + + if (type == 'PNG') { + //png - default format + if (this.Helper.strpos(fname, '.png') == false) + fname = fname + ".png"; + + //simple save example + //var link = document.createElement('a'); + //link.download = fname; + //link.href = canvas.toDataURL(); + //link.click(); + + //save using lib + canvas.toBlob(function (blob) { + filesaver.saveAs(blob, fname); + }); + } + else if (type == 'JPG') { + //jpg + if (this.Helper.strpos(fname, '.jpg') == false) + fname = fname + ".jpg"; + + canvas.toBlob(function (blob) { + filesaver.saveAs(blob, fname); + }, "image/jpeg", quality); + } + else if (type == 'WEBP') { + //WEBP + if (this.Helper.strpos(fname, '.webp') == false) + fname = fname + ".webp"; + var data_header = "image/webp"; + + //check support + if (this.check_format_support(canvas, data_header) == false) + return false; + + canvas.toBlob(function (blob) { + filesaver.saveAs(blob, fname); + }, data_header, quality); + } + else if (type == 'AVIF') { + //AVIF + if (this.Helper.strpos(fname, '.avif') == false) + fname = fname + ".avif"; + var data_header = "image/avif"; + + //check support + if (this.check_format_support(canvas, data_header) == false) + return false; + + canvas.toBlob(function (blob) { + filesaver.saveAs(blob, fname); + }, data_header, quality); + } + else if (type == 'BMP') { + //bmp + if (this.Helper.strpos(fname, '.bmp') == false) + fname = fname + ".bmp"; + var data_header = "image/bmp"; + + //check support + if (this.check_format_support(canvas, data_header) == false) + return false; + + canvas.toBlob(function (blob) { + filesaver.saveAs(blob, fname); + }, data_header); + } + else if (type == 'TIFF') { + //tiff + if (this.Helper.strpos(fname, '.tiff') == false) + fname = fname + ".tiff"; + var data_header = "image/tiff"; + + CanvasToTIFF.toBlob(canvas, function(blob) { + filesaver.saveAs(blob, fname); + }, data_header); + } + else if (type == 'JSON') { + //json - full data with layers + if (this.Helper.strpos(fname, '.json') == false) + fname = fname + ".json"; + + var data_json = this.export_as_json(); + + var blob = new Blob([data_json], {type: "text/plain"}); + //var data = window.URL.createObjectURL(blob); //html5 + filesaver.saveAs(blob, fname); + } + else if (type == 'GIF') { + //gif + var cores = navigator.hardwareConcurrency || 4; + var gif_settings = { + workers: cores, + quality: 10, //1-30, lower is better + repeat: 0, + width: config.WIDTH, + height: config.HEIGHT, + dither: 'FloydSteinberg-serpentine', + workerScript: './src/js/libs/gifjs/gif.worker.js', + }; + if (config.TRANSPARENCY == true) { + gif_settings.transparent = 'rgba(0,0,0,0)'; + } + var gif = new GIF(gif_settings); + + //add frames + for (var i = 0; i < config.layers.length; i++) { + if (config.layers[i].visible == false) + continue; + + ctx.clearRect(0, 0, config.WIDTH, config.HEIGHT); + if (config.TRANSPARENCY == false) { + this.fillCanvasBackground(ctx, '#ffffff'); + } + this.Base_layers.convert_layers_to_canvas(ctx, config.layers[i].id, false); + + gif.addFrame(ctx, {copy: true, delay: delay}); + } + gif.render(); + gif.on('finished', function (blob) { + filesaver.saveAs(blob, fname); + }); + } + } + + fillCanvasBackground(ctx, color, width = config.WIDTH, height = config.HEIGHT) { + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.fillStyle = color; + ctx.fill(); + } + + check_format_support(canvas, data_header, show_error) { + var data = canvas.toDataURL(data_header); + var actualType = data.replace(/^data:([^;]*).*/, '$1'); + + if (data_header != actualType && data_header != "text/plain") { + if (show_error == undefined || show_error == true) { + //error - no support + alertify.error('Your browser does not support this format.'); + } + return false; + } + return true; + } + + /** + * exports all layers to JSON + */ + export_as_json() { + //get date + var today = new Date(); + var yyyy = today.getFullYear(); + var mm = today.getMonth() + 1; //January is 0! + var dd = today.getDate(); + if (dd < 10) + dd = '0' + dd; + if (mm < 10) + mm = '0' + mm; + var today = yyyy + '-' + mm + '-' + dd; + + //data + var export_data = {}; + export_data.info = { + width: config.WIDTH, + height: config.HEIGHT, + about: 'Image data with multi-layers. Can be opened using miniPaint - ' + + 'https://github.com/viliusle/miniPaint', + date: today, + version: VERSION, + layer_active: config.layer.id, + guides: config.guides, + }; + + //fonts + export_data.user_fonts = config.user_fonts; + + //layers + export_data.layers = []; + for (var i in config.layers) { + var layer = {}; + for (var j in config.layers[i]) { + if (j[0] == '_' || j == 'link_canvas') { + //private data + continue; + } + + layer[j] = config.layers[i][j]; + } + export_data.layers.push(layer); + } + + //image data + export_data.data = []; + for (var i in config.layers) { + if (config.layers[i].type != 'image') + continue; + + var canvas = document.createElement('canvas'); + canvas.width = config.layers[i].width_original; + canvas.height = config.layers[i].height_original; + this.disable_canvas_smooth(canvas.getContext("2d")); + + canvas.getContext('2d').drawImage(config.layers[i].link, 0, 0); + + var data_tmp = canvas.toDataURL("image/png"); + export_data.data.push( + { + id: config.layers[i].id, + data: data_tmp, + } + ); + canvas.width = 1; + canvas.height = 1; + } + + return JSON.stringify(export_data, null, "\t"); + } + + /** + * removes smoothing, because it look ugly during zoom + * + * @param {ctx} ctx + */ + disable_canvas_smooth(ctx) { + ctx.webkitImageSmoothingEnabled = false; + ctx.oImageSmoothingEnabled = false; + ctx.msImageSmoothingEnabled = false; + ctx.imageSmoothingEnabled = false; + } + +} + +export default File_save_class; \ No newline at end of file diff --git a/frontend/src/js/modules/help/about.js b/frontend/src/js/modules/help/about.js new file mode 100644 index 0000000..192984a --- /dev/null +++ b/frontend/src/js/modules/help/about.js @@ -0,0 +1,32 @@ +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; + +class Help_about_class { + + constructor() { + this.POP = new Dialog_class(); + } + + //about + about() { + var email = 'www.viliusl@gmail.com'; + + var settings = { + title: 'About', + params: [ + {title: "", html: ''}, + {title: "Name:", html: 'miniPaint'}, + {title: "Version:", value: VERSION}, + {title: "Description:", value: "Online image editor."}, + {title: "Author:", value: 'ViliusL'}, + {title: "Email:", html: '' + email + ''}, + {title: "GitHub:", html: 'https://github.com/viliusle/miniPaint'}, + {title: "Website:", html: 'https://viliusle.github.io/miniPaint/'}, + ], + }; + this.POP.show(settings); + } + +} + +export default Help_about_class; diff --git a/frontend/src/js/modules/help/shortcuts.js b/frontend/src/js/modules/help/shortcuts.js new file mode 100644 index 0000000..0c05a62 --- /dev/null +++ b/frontend/src/js/modules/help/shortcuts.js @@ -0,0 +1,44 @@ +import Dialog_class from './../../libs/popup.js'; + +class Help_shortcuts_class { + + constructor() { + this.POP = new Dialog_class(); + } + + //shortcuts + shortcuts() { + var settings = { + title: 'Keyboard Shortcuts', + className: 'shortcuts', + params: [ + {title: "F", value: 'Auto Adjust Colors'}, + {title: "F3 / ⌘ + F", value: 'Search'}, + {title: "Ctrl + C", value: 'Copy to Clipboard'}, + {title: "D", value: 'Duplicate'}, + {title: "S", value: 'Export'}, + {title: "G", value: 'Grid on/off'}, + {title: "I", value: 'Information'}, + {title: "N", value: 'New layer'}, + {title: "O", value: 'Open'}, + {title: "CTRL + V", value: 'Paste'}, + {title: "F10", value: 'Quick Load'}, + {title: "F9", value: 'Quick Save'}, + {title: "R", value: 'Resize'}, + {title: "L", value: 'Rotate left'}, + {title: "U", value: 'Ruler'}, + {title: "Shift + S", value: 'Save As'}, + {title: "CTRL + A", value: 'Select All'}, + {title: "H", value: 'Shapes'}, + {title: "T", value: 'Trim'}, + {title: "CTRL + Z", value: 'Undo'}, + {title: "Scroll up", value: 'Zoom in'}, + {title: "Scroll down", value: 'Zoom out'}, + ], + }; + this.POP.show(settings); + } + +} + +export default Help_shortcuts_class; diff --git a/frontend/src/js/modules/image/auto_adjust.js b/frontend/src/js/modules/image/auto_adjust.js new file mode 100644 index 0000000..4bda199 --- /dev/null +++ b/frontend/src/js/modules/image/auto_adjust.js @@ -0,0 +1,168 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Dialog_class from './../../libs/popup.js'; +import Helper_class from './../../libs/helpers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +var instance = null; + +class Image_autoAdjust_class { + + constructor() { + //singleton + if (instance) { + return instance; + } + instance = this; + + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + + this.set_events(); + } + + set_events() { + document.addEventListener('keydown', (event) => { + var code = event.keyCode; + if (this.Helper.is_input(event.target)) + return; + + if (code == 70 && event.ctrlKey != true && event.metaKey != true) { + //F - adjust + this.auto_adjust(); + event.preventDefault(); + } + }, false); + } + + auto_adjust() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.get_adjust_data(img); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + get_adjust_data(data) { + //settings + var white = 240; //white color min + var black = 30; //black color max + var target_white = 1; //how much % white colors should take + var target_black = 0.5; //how much % black colors should take + var modify = 1.1; //color modify strength + var cycles_count = 10; //how much iteration to change colors + + var imgData = data.data; + var W = data.width; + var H = data.height; + + var n = 0; //pixels count without transparent + + //make sure we have white + var n_valid = 0; + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + if ((imgData[i] + imgData[i + 1] + imgData[i + 2]) / 3 > white) + n_valid++; + n++; + } + var target = target_white; + var n_fix_white = 0; + var done = false; + for (var j = 0; j < cycles_count; j++) { + if (n_valid * 100 / n >= target) + done = true; + if (done == true) + break; + n_fix_white++; + + //adjust + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + for (var c = 0; c < 3; c++) { + var x = i + c; + if (imgData[x] < 10) + continue; + //increase white + imgData[x] *= modify; + imgData[x] = Math.round(imgData[x]); + if (imgData[x] > 255) + imgData[x] = 255; + } + } + + //recheck + n_valid = 0; + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + if ((imgData[i] + imgData[i + 1] + imgData[i + 2]) / 3 > white) + n_valid++; + } + } + + //make sure we have black + n_valid = 0; + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + if ((imgData[i] + imgData[i + 1] + imgData[i + 2]) / 3 < black) + n_valid++; + } + target = target_black; + var n_fix_black = 0; + var done = false; + for (var j = 0; j < cycles_count; j++) { + if (n_valid * 100 / n >= target) + done = true; + if (done == true) + break; + n_fix_black++; + + //adjust + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + for (var c = 0; c < 3; c++) { + var x = i + c; + if (imgData[x] > 240) + continue; + //increase black + imgData[x] -= (255 - imgData[x]) * modify - (255 - imgData[x]); + imgData[x] = Math.round(imgData[x]); + } + } + + //recheck + n_valid = 0; + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + if ((imgData[i] + imgData[i + 1] + imgData[i + 2]) / 3 < black) + n_valid++; + } + } + //log('Iterations: brighten='+n_fix_white+", darken="+n_fix_black); + + return data; + } +} + +export default Image_autoAdjust_class; \ No newline at end of file diff --git a/frontend/src/js/modules/image/color_corrections.js b/frontend/src/js/modules/image/color_corrections.js new file mode 100644 index 0000000..79de61c --- /dev/null +++ b/frontend/src/js/modules/image/color_corrections.js @@ -0,0 +1,134 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Dialog_class from './../../libs/popup.js'; +import Helper_class from './../../libs/helpers.js'; +import ImageFilters_class from './../../libs/imagefilters.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Image_colorCorrections_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.ImageFilters = ImageFilters_class; + } + + color_corrections() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Color Corrections', + preview: true, + on_change: function (params, canvas_preview, w, h, canvas) { + //destructive effects + var img = this.layer_active_small_ctx.getImageData(0, 0, w, h); + var data = _this.do_corrections(img, params, false); + canvas_preview.putImageData(data, 0, 0); + + //non-destructive + canvas_preview.filter = "brightness(" + (1 + (params.param_b / 100)) + ")"; + canvas_preview.filter += " contrast(" + (1 + (params.param_c / 100)) + ")"; + canvas_preview.filter += " saturate(" + (1 + (params.param_s / 100)) + ")"; + canvas_preview.filter += " hue-rotate(" + params.param_h + "deg)"; + + canvas_preview.drawImage(canvas, 0, 0); + }, + params: [ + {name: "param_b", title: "Brightness:", value: "0", range: [-100, 100]}, + {name: "param_c", title: "Contrast:", value: "0", range: [-100, 100]}, + {name: "param_s", title: "Saturation:", value: "0", range: [-100, 100]}, + {name: "param_h", title: "Hue:", value: "0", range: [-180, 180]}, + {}, + {name: "param_l", title: "Luminance:", value: "0", range: [-100, 100]}, + {}, + {name: "param_red", title: "Red channel:", value: "0", range: [-255, 255]}, + {name: "param_green", title: "Green channel:", value: "0", range: [-255, 255]}, + {name: "param_blue", title: "Blue channel:", value: "0", range: [-255, 255]}, + ], + on_finish: function (params) { + _this.save_changes(params); + }, + }; + this.POP.show(settings); + } + + save_changes(params) { + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.do_corrections(img, params); + ctx.putImageData(data, 0, 0); + + //save + app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + + //non-destructive filters + //multiple do_action() + do_corrections() does not work together yet. + if(params.param_b != 0) { + var parameters = {value: params.param_b}; + var filter_id = null; + app.State.do_action( + new app.Actions.Add_layer_filter_action(null, 'brightness', parameters, filter_id) + ); + } + if(params.param_c != 0) { + var parameters = {value: params.param_c}; + var filter_id = null; + app.State.do_action( + new app.Actions.Add_layer_filter_action(null, 'contrast', parameters, filter_id) + ); + } + if(params.param_s != 0) { + var parameters = {value: params.param_s}; + var filter_id = null; + app.State.do_action( + new app.Actions.Add_layer_filter_action(null, 'saturate', parameters, filter_id) + ); + } + if(params.param_h != 0) { + var parameters = {value: params.param_h}; + var filter_id = null; + app.State.do_action( + new app.Actions.Add_layer_filter_action(null, 'hue-rotate', parameters, filter_id) + ); + } + } + + /** + * corrections (destructive) + * + * @param data + * @param params + * @returns {*} + */ + do_corrections(data, params) { + //luminance + if(params.param_l != 0) { + var data = this.ImageFilters.HSLAdjustment(data, 0, 0, params.param_l); + } + + //RGB corrections + if(params.param_red != 0 || params.param_green != 0 || params.param_blue != 0) { + var data = this.ImageFilters.ColorTransformFilter(data, 1, 1, 1, 1, + params.param_red, params.param_green, params.param_blue, 1); + } + + return data; + } + +} + +export default Image_colorCorrections_class; \ No newline at end of file diff --git a/frontend/src/js/modules/image/decrease_colors.js b/frontend/src/js/modules/image/decrease_colors.js new file mode 100644 index 0000000..d81fb7a --- /dev/null +++ b/frontend/src/js/modules/image/decrease_colors.js @@ -0,0 +1,174 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Dialog_class from './../../libs/popup.js'; +import Helper_class from './../../libs/helpers.js'; +import ImageFilters_class from './../../libs/imagefilters.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Image_decreaseColors_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.ImageFilters = ImageFilters_class; + } + + decrease_colors() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Decrease Color Depth', + preview: true, + on_change: function (params, canvas_preview, w, h) { + var img = canvas_preview.getImageData(0, 0, w, h); + var data = _this.get_decreased_data(img, params.colors, params.greyscale); + canvas_preview.putImageData(data, 0, 0); + }, + params: [ + {name: "colors", title: "Colors:", value: 10, range: [1, 256]}, + {name: "greyscale", title: "Greyscale:", value: false}, + ], + on_finish: function (params) { + _this.execute(params); + }, + }; + this.POP.show(settings); + } + + execute(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.get_decreased_data(img, params.colors, params.greyscale); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + get_decreased_data(data, colors, greyscale) { + var img = data.data; + var imgData = data.data; + var W = data.width; + var H = data.height; + var palette = []; + var block_size = 10; + + //create tmp canvas + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext("2d"); + canvas.width = W; + canvas.height = H; + + //collect top colors + ctx.drawImage(config.layer.link, 0, 0, Math.ceil(W / block_size), Math.ceil(H / block_size)); + var img_p = ctx.getImageData(0, 0, Math.ceil(W / block_size), Math.ceil(H / block_size)); + var imgData_p = img_p.data; + ctx.clearRect(0, 0, W, H); + + for (var i = 0; i < imgData_p.length; i += 4) { + if (imgData_p[i + 3] == 0) + continue; //transparent + var grey = Math.round(0.2126 * imgData_p[i] + 0.7152 * imgData_p[i + 1] + + 0.0722 * imgData_p[i + 2]); + palette.push([imgData_p[i], imgData_p[i + 1], imgData_p[i + 2], grey]); + } + + //calculate weights + var grey_palette = []; + for (var i = 0; i < 256; i++) + grey_palette[i] = 0; + for (var i = 0; i < palette.length; i++) + grey_palette[palette[i][3]]++; + + //remove similar colors + for (var max = 10 * 3; max < 100 * 3; max = max + 10 * 3) { + if (palette.length <= colors) + break; + for (var i = 0; i < palette.length; i++) { + if (palette.length <= colors) + break; + var valid = true; + for (var j = 0; j < palette.length; j++) { + if (palette.length <= colors) + break; + if (i == j) + continue; + if (Math.abs(palette[i][0] - palette[j][0]) + + Math.abs(palette[i][1] - palette[j][1]) + + Math.abs(palette[i][2] - palette[j][2]) < max) { + if (grey_palette[palette[i][3]] > grey_palette[palette[j][3]]) { + //remove color + palette.splice(j, 1); + j--; + } + else { + valid = false; + break; + } + } + } + //remove color + if (valid == false) { + palette.splice(i, 1); + i--; + } + } + } + palette = palette.slice(0, colors); + + //change + var p_n = palette.length; + for (var j = 0; j < H; j++) { + for (var i = 0; i < W; i++) { + var k = ((j * (W * 4)) + (i * 4)); + if (imgData[k + 3] == 0) + continue; //transparent + + //find closest color + var index1 = 0; + var min = 999999; + var diff1; + for (var m = 0; m < p_n; m++) { + var diff = Math.abs(palette[m][0] - imgData[k]) + + Math.abs(palette[m][1] - imgData[k + 1]) + + Math.abs(palette[m][2] - imgData[k + 2]); + if (diff < min) { + min = diff; + index1 = m; + diff1 = diff; + } + } + + imgData[k] = palette[index1][0]; + imgData[k + 1] = palette[index1][1]; + imgData[k + 2] = palette[index1][2]; + + if (greyscale == true) { + var mid = Math.round(0.2126 * imgData[k] + 0.7152 * imgData[k + 1] + + 0.0722 * imgData[k + 2]); + imgData[k] = mid; + imgData[k + 1] = mid; + imgData[k + 2] = mid; + } + } + } + + return data; + } + +} + +export default Image_decreaseColors_class; \ No newline at end of file diff --git a/frontend/src/js/modules/image/flip.js b/frontend/src/js/modules/image/flip.js new file mode 100644 index 0000000..3fa7b34 --- /dev/null +++ b/frontend/src/js/modules/image/flip.js @@ -0,0 +1,56 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Image_flip_class { + + constructor() { + this.Base_layers = new Base_layers_class(); + } + + vertical() { + this.flip('vertical'); + } + + horizontal() { + this.flip('horizontal'); + } + + flip(mode) { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //create destination canvas + var canvas2 = document.createElement('canvas'); + canvas2.width = canvas.width; + canvas2.height = canvas.height; + var ctx2 = canvas2.getContext("2d"); + canvas2.dataset.x = canvas.dataset.x; + canvas2.dataset.y = canvas.dataset.y; + + //flip + if (mode == 'vertical') { + ctx2.scale(1, -1); + ctx2.drawImage(canvas, 0, canvas2.height * -1); + } + else if (mode == 'horizontal') { + ctx2.scale(-1, 1); + ctx2.drawImage(canvas, canvas2.width * -1, 0); + } + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas2) + ); + } + +} + +export default Image_flip_class; \ No newline at end of file diff --git a/frontend/src/js/modules/image/histogram.js b/frontend/src/js/modules/image/histogram.js new file mode 100644 index 0000000..f9786c5 --- /dev/null +++ b/frontend/src/js/modules/image/histogram.js @@ -0,0 +1,122 @@ +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Dialog_class from './../../libs/popup.js'; +import Helper_class from './../../libs/helpers.js'; + +class Image_histogram_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + } + + histogram() { + var _this = this; + + var settings = { + title: 'Histogram', + on_change: function (params) { + _this.histogram_onload(params); + }, + params: [ + {name: "channel", title: "Channel:", values: ["Gray", "Red", "Green", "Blue"], }, + {title: 'Histogram:', function: function () { + var html = ''; + return html; + }}, + {title: "Total pixels:", value: ""}, + {title: "Average:", value: ""}, + ], + }; + this.POP.show(settings); + + this.histogram_onload({}); + } + + histogram_onload(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(config.layer.id); + var ctx = canvas.getContext("2d"); + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var imgData = img.data; + + var channel = 0; + if (params.channel == 'Red') + channel = 1; + else if (params.channel == 'Green') + channel = 2; + else if (params.channel == 'Blue') + channel = 3; + + var hist_data = [[], [], [], []]; //grey, red, green, blue + var total = imgData.length / 4; + var sum = 0; + var grey; + + for (var i = 0; i < imgData.length; i += 4) { + //collect grey + grey = Math.round((imgData[i] + imgData[i + 1] + imgData[i + 2]) / 3); + sum = sum + imgData[i] + imgData[i + 1] + imgData[i + 2]; + if (hist_data[0][grey] == undefined) + hist_data[0][grey] = 1; + else + hist_data[0][grey]++; + + //collect colors + for (var c = 0; c < 3; c++) { + if (c + 1 != channel) + continue; + if (hist_data[c + 1][imgData[i + c]] == undefined) + hist_data[c + 1][imgData[i + c]] = 1; + else + hist_data[c + 1][imgData[i + c]]++; + } + } + + var c = document.getElementById("c_h").getContext("2d"); + c.rect(0, 0, 256, 100); + c.fillStyle = "#ffffff"; + c.fill(); + var opacity = 1; + + //draw histogram + for (var h in hist_data) { + for (var i = 0; i <= 255; i++) { + if (h != channel) + continue; + if (hist_data[h][i] == 0) + continue; + c.beginPath(); + + if (h == 0) + c.strokeStyle = "rgba(64, 64, 64, " + opacity * 2 + ")"; + else if (h == 1) + c.strokeStyle = "rgba(255, 0, 0, " + opacity + ")"; + else if (h == 2) + c.strokeStyle = "rgba(0, 255, 0, " + opacity + ")"; + else if (h == 3) + c.strokeStyle = "rgba(0, 0, 255, " + opacity + ")"; + + c.lineWidth = 1; + c.moveTo(i + 0.5, 100 + 0.5); + c.lineTo(i + 0.5, 100 - Math.round(hist_data[h][i] * 255 * 100 / total / 6) + 0.5); + c.stroke(); + } + } + + document.getElementById("pop_data_totalpixel").innerHTML = this.Helper.number_format(total, 0); + var average; + if (total > 0) + average = Math.round(sum * 10 / total / 3) / 10; + else + average = '-'; + document.getElementById("pop_data_average").innerHTML = average; + + canvas.width = 1; + canvas.height = 1; + } + +} + +export default Image_histogram_class; \ No newline at end of file diff --git a/frontend/src/js/modules/image/information.js b/frontend/src/js/modules/image/information.js new file mode 100644 index 0000000..da7a307 --- /dev/null +++ b/frontend/src/js/modules/image/information.js @@ -0,0 +1,144 @@ +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Helper_class from './../../libs/helpers.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Tools_settings_class from './../tools/settings.js'; + +var instance = null; + +class Image_information_class { + + constructor() { + //singleton + if (instance) { + return instance; + } + instance = this; + + this.Base_layers = new Base_layers_class(); + this.POP = new Dialog_class(); + this.Helper = new Helper_class(); + this.Tools_settings = new Tools_settings_class(); + + this.set_events(); + } + + set_events() { + document.addEventListener('keydown', (event) => { + var code = event.key.toLowerCase(); + if (this.Helper.is_input(event.target)) + return; + + if (code == "i") { + this.information(); + event.preventDefault(); + } + }, false); + } + + information() { + var _this = this; + var pixels = config.WIDTH * config.HEIGHT; + pixels = this.Helper.number_format(pixels, 0); + + var units = this.Tools_settings.get_setting('default_units'); + var resolution = this.Tools_settings.get_setting('resolution'); + + var width = this.Helper.get_user_unit(config.WIDTH, units, resolution); + var height = this.Helper.get_user_unit(config.HEIGHT, units, resolution); + + var settings = { + title: 'Information', + params: [ + {title: "Width:", value: width + ' ' + units}, + {title: "Height:", value: height + ' ' + units}, + {title: "Pixels:", value: pixels}, + {title: "Layers:", value: config.layers.length}, + {title: "Unique colors:", value: '...'}, + ], + }; + if(units != 'pixels'){ + settings.params[0].value += " (" + config.WIDTH + " pixels)"; + settings.params[1].value += " (" + config.HEIGHT + " pixels)"; + } + + //exif data + if (config.layer._exif != undefined) { + //show exif and general data + var exif_data = config.layer._exif; + + //show general data + for (var i in exif_data.general) { + settings.params.push({title: i + ":", value: exif_data.general[i]}); + } + + //show exif data + var n = 0; + for (var i in exif_data.exif) { + if (i == 'undefined') + continue; + if (n == 0) + settings.params.push({title: "==== EXIF ====", value: ''}); + settings.params.push({title: i + ":", value: exif_data.exif[i]}); + n++; + } + } + + this.POP.show(settings); + + //calc colors + setTimeout(function () { + var colors = _this.unique_colors_count(); + colors = _this.Helper.number_format(colors, 0); + document.getElementById('pop_data_uniquecolo').innerHTML = colors; + }, 50); + } + + unique_colors_count() { + var method = 'v2'; //v1 or v2 + + if (config.WIDTH * config.HEIGHT > 20 * 1000 * 1000) { + return '-'; + } + + var canvas = this.Base_layers.convert_layer_to_canvas(); + var ctx = canvas.getContext("2d"); + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var imgData = img.data; + + //v1 - simple, slow + if (method == 'v1') { + var colors = []; + var n = 0; + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + var key = imgData[i] + "." + imgData[i + 1] + "." + imgData[i + 2]; + if (colors[key] == undefined) { + colors[key] = 1; + n++; + } + } + } + + //v2 - 30% faster + else if (method == 'v2') { + var buffer32 = new Uint32Array(imgData.buffer); + var len = buffer32.length; + var stats = {}; + var n = 0; + + for (var i = 0; i < len; i++) { + var key = "" + (buffer32[i] & 0xffffff); + if (stats[key] == undefined) { + stats[key] = 0; + n++; + } + } + } + + return n; + } +} + +export default Image_information_class; diff --git a/frontend/src/js/modules/image/opacity.js b/frontend/src/js/modules/image/opacity.js new file mode 100644 index 0000000..0a73aa4 --- /dev/null +++ b/frontend/src/js/modules/image/opacity.js @@ -0,0 +1,56 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; + +class Image_opacity_class { + + constructor() { + this.POP = new Dialog_class(); + } + + opacity() { + var _this = this; + var initial_opacity = config.layer.opacity; + + var settings = { + title: 'Opacity', + params: [ + {name: "opacity", title: "Alpha:", value: config.layer.opacity, range: [0, 100]}, + ], + on_change: function (params, canvas_preview, w, h) { + _this.opacity_handler(params, false); + }, + on_finish: function (params) { + config.layer.opacity = initial_opacity; + _this.opacity_handler(params); + }, + on_cancel: function (params) { + config.layer.opacity = initial_opacity; + config.need_render = true; + }, + }; + this.POP.show(settings); + } + + opacity_handler(data, is_final = true) { + var value = parseInt(data.opacity); + if (value < 0) + value = 0; + if (value > 100) + value = 100; + if (is_final) { + app.State.do_action( + new app.Actions.Bundle_action('change_opacity', 'Change Opacity', [ + new app.Actions.Update_layer_action(config.layer.id, { + opacity: value + }) + ]) + ); + } else { + config.layer.opacity = value; + config.need_render = true; + } + } +} + +export default Image_opacity_class; diff --git a/frontend/src/js/modules/image/palette.js b/frontend/src/js/modules/image/palette.js new file mode 100644 index 0000000..0d7ca80 --- /dev/null +++ b/frontend/src/js/modules/image/palette.js @@ -0,0 +1,53 @@ +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import colorThief_class from './../../libs/color-thief.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; +import Dialog_class from './../../libs/popup.js'; +import Helper_class from './../../libs/helpers.js'; + +class Image_color_class { + + constructor() { + this.Base_layers = new Base_layers_class(); + this.alertify = new colorThief_class(); + this.POP = new Dialog_class(); + this.Helper = new Helper_class(); + } + + palette() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + var palette = this.alertify.getPalette(config.layer.link); + var dominant = this.alertify.getColor(config.layer.link); + dominant = this.Helper.rgbToHex(dominant[0], dominant[1], dominant[2]); + + var settings = { + title: 'Palette', + params: [ + {title: "Dominant color:", html: this.generate_color_box(dominant, 200)}, + ], + }; + for (var i in palette) { + var rgb = this.Helper.rgbToHex(palette[i][0], palette[i][1], palette[i][2]); + i = parseInt(i); + settings.params.push( + {title: "Color #" + (i + 1) + ":", html: this.generate_color_box(rgb, 100)} + ); + } + this.POP.show(settings); + } + + generate_color_box(color, width) { + var html = ''; + + html += ''; + html += ''; + + return html; + } + +} + +export default Image_color_class; diff --git a/frontend/src/js/modules/image/resize.js b/frontend/src/js/modules/image/resize.js new file mode 100644 index 0000000..61a5f61 --- /dev/null +++ b/frontend/src/js/modules/image/resize.js @@ -0,0 +1,329 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Base_gui_class from './../../core/base-gui.js'; +import Dialog_class from './../../libs/popup.js'; +import ImageFilters_class from './../../libs/imagefilters.js'; +import Hermite_class from 'hermite-resize'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; +import Pica from './../../../../node_modules/pica/dist/pica.js'; +import Helper_class from './../../libs/helpers.js'; +import Tools_settings_class from './../tools/settings.js'; +import { metaDefaults as textMetaDefaults } from '../../tools/text.js'; + +var instance = null; + +class Image_resize_class { + + constructor() { + //singleton + if (instance) { + return instance; + } + instance = this; + + this.Base_layers = new Base_layers_class(); + this.Base_gui = new Base_gui_class(); + this.POP = new Dialog_class(); + this.ImageFilters = ImageFilters_class; + this.Hermite = new Hermite_class(); + this.Tools_settings = new Tools_settings_class(); + this.pica = Pica(); + this.Helper = new Helper_class(); + + this.set_events(); + } + + set_events() { + document.addEventListener('keydown', (event) => { + var code = event.keyCode; + if (this.Helper.is_input(event.target)) + return; + + if (code == 82 && event.ctrlKey != true && event.metaKey != true) { + //R - resize + this.resize(); + event.preventDefault(); + } + }, false); + } + + resize() { + var _this = this; + var units = this.Tools_settings.get_setting('default_units'); + var resolution = this.Tools_settings.get_setting('resolution'); + + //convert units + var width = this.Helper.get_user_unit(config.WIDTH, units, resolution); + var height = this.Helper.get_user_unit(config.HEIGHT, units, resolution); + + var settings = { + title: 'Resize', + params: [ + {name: "width", title: "Width:", value: '', placeholder: width, comment: units}, + {name: "height", title: "Height:", value: '', placeholder: height, comment: units}, + {name: "width_percent", title: "Width (%):", value: '', placeholder: 100, comment: "%"}, + {name: "height_percent", title: "Height (%):", value: '', placeholder: 100, comment: "%"}, + {name: "mode", title: "Mode:", values: ["Lanczos", "Hermite", "Basic"]}, + + {name: "sharpen", title: "Sharpen:", value: false}, + {name: "layers", title: "Layers:", values: ["All", "Active"], value: "All"}, + ], + on_finish: function (params) { + _this.do_resize(params); + }, + }; + this.POP.show(settings); + + document.getElementById("pop_data_width").select(); + } + + async do_resize(params) { + //validate + if (isNaN(params.width) && isNaN(params.height) && isNaN(params.width_percent) && isNaN(params.height_percent)) { + alertify.error('Missing at least 1 size parameter.'); + return false; + } + + // Build a list of actions to execute for resize + let actions = []; + + if (params.layers == 'All') { + //resize all layers + var skips = 0; + for (var i in config.layers) { + try { + actions = actions.concat(await this.resize_layer(config.layers[i], params)); + } catch (error) { + skips++; + } + } + if (skips > 0) { + alertify.error(skips + ' layer(s) were skipped.'); + } + actions = actions.concat(this.resize_gui(params)); + } + else { + //only active + actions = actions.concat(await this.resize_layer(config.layer, params)); + } + return app.State.do_action( + new app.Actions.Bundle_action('resize_layers', 'Resize Layers', actions) + ); + } + + /** + * Generates actions that will resize layer (image, text, vector), returns a promise that rejects on failure. + * + * @param {object} layer + * @param {object} params + * @returns {Promise} Returns array of actions to perform + */ + async resize_layer(layer, params) { + var units = this.Tools_settings.get_setting('default_units'); + var resolution = this.Tools_settings.get_setting('resolution'); + var mode = params.mode; + var width = parseFloat(params.width); + var height = parseFloat(params.height); + var width_100 = parseInt(params.width_percent); + var height_100 = parseInt(params.height_percent); + var canvas_width = layer.width; + var canvas_height = layer.height; + var sharpen = params.sharpen; + var _this = this; + + //convert units + if (isNaN(width) == false){ + width = this.Helper.get_internal_unit(width, units, resolution); + } + if (isNaN(height) == false){ + height = this.Helper.get_internal_unit(height, units, resolution); + } + + //if dimension with percent provided + if (isNaN(width) && isNaN(height)) { + if (isNaN(width_100) == false) { + width = Math.round(config.WIDTH * width_100 / 100); + canvas_width = Math.round(config.WIDTH * width_100 / 100); + } + if (isNaN(height_100) == false) { + height = Math.round(config.HEIGHT * height_100 / 100); + canvas_height = Math.round(config.HEIGHT * height_100 / 100); + } + } + + //if only 1 dimension was provided + if (isNaN(width) || isNaN(height)) { + var ratio = layer.width / layer.height; + var canvas_ratio = config.WIDTH / config.HEIGHT; + if (isNaN(width)) + width = Math.round(height * ratio); + canvas_width = Math.round(canvas_height * canvas_ratio); + if (isNaN(height)) + height = Math.round(width / ratio); + canvas_height = Math.round(canvas_width / canvas_ratio); + } + + let new_x = params.layers == 'All' ? Math.round(layer.x * width / config.WIDTH) : layer.x; + let new_y = params.layers == 'All' ? Math.round(layer.y * height / config.HEIGHT) : layer.y; + let xratio = width / config.WIDTH; + let yratio = height / config.HEIGHT; + + //is text + if (layer.type == 'text') { + let data = JSON.parse(JSON.stringify(layer.data)); + for (let line of data) { + for (let span of line) { + span.meta.size = Math.ceil((span.meta.size || textMetaDefaults.size) * xratio); + span.meta.stroke_size = parseFloat((0.1 * Math.round((span.meta.stroke_size != null ? span.meta.stroke_size : textMetaDefaults.stroke_size) * xratio / 0.1)).toFixed(1)); + span.meta.kerning = Math.ceil((span.meta.kerning || textMetaDefaults.kerning) * xratio); + } + } + + // Return actions + return [ + new app.Actions.Update_layer_action(layer.id, { + x: new_x, + y: new_y, + data, + width: layer.width * xratio, + height: layer.height * yratio + }) + ]; + } + + //is vector + else if (layer.is_vector == true && layer.width != null && layer.height != null) { + // Return actions + return [ + new app.Actions.Update_layer_action(layer.id, { + x: new_x, + y: new_y, + width: layer.width * xratio, + height: layer.height * yratio + }) + ]; + } + + //only images supported at this point + else if (layer.type != 'image') { + //error - no support + alertify.error('Layer must be vector or image (convert it to raster).'); + throw new Error('Layer is not compatible with resize'); + } + + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(layer.id, true, false); + var ctx = canvas.getContext("2d"); + + //validate + if (mode == "Hermite" && (width > canvas.width || height > canvas.height)) { + alertify.warning('Scaling up is not supported in Hermite, using Lanczos.'); + mode = "Lanczos"; + } + + //resize + if (mode == "Lanczos") { + //Pica resize with max quality + + var tmp_data = document.createElement("canvas"); + tmp_data.width = width; + tmp_data.height = height; + + await this.pica.resize(canvas, tmp_data, { + alpha: true, + }) + .then((result) => { + ctx.clearRect(0, 0, canvas.width, canvas.height); + canvas.width = width; + canvas.height = height; + ctx.drawImage(tmp_data, 0, 0, width, height); + }); + } + else if (mode == "Hermite") { + //Hermite resample + this.Hermite.resample_single(canvas, width, height, true); + } + else { + //simple resize + var tmp_data = document.createElement("canvas"); + tmp_data.width = canvas.width; + tmp_data.height = canvas.height; + tmp_data.getContext("2d").drawImage(canvas, 0, 0); + + ctx.clearRect(0, 0, canvas.width, canvas.height); + canvas.width = width; + canvas.height = height; + + ctx.drawImage(tmp_data, 0, 0, width, height); + } + + if (sharpen == true) { + var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + var filtered = _this.ImageFilters.Sharpen(imageData, 1); //add effect + ctx.putImageData(filtered, 0, 0); + } + + // Return actions + return [ + new app.Actions.Update_layer_image_action(canvas, layer.id), + new app.Actions.Update_layer_action(layer.id, { + x: new_x, + y: new_y, + width: canvas.width, + height: canvas.height, + width_original: canvas.width, + height_original: canvas.height + }) + ]; + } + + resize_gui(params) { + var units = this.Tools_settings.get_setting('default_units'); + var resolution = this.Tools_settings.get_setting('resolution'); + + var width = parseFloat(params.width); + var height = parseFloat(params.height); + var width_100 = parseInt(params.width_percent); + var height_100 = parseInt(params.height_percent); + + //convert units + if (isNaN(width) == false){ + width = this.Helper.get_internal_unit(width, units, resolution); + } + if (isNaN(height) == false){ + height = this.Helper.get_internal_unit(height, units, resolution); + } + + //if dimension with percent provided + if (isNaN(width) && isNaN(height)) { + if (isNaN(width_100) == false) { + width = Math.round(config.WIDTH * width_100 / 100); + } + if (isNaN(height_100) == false) { + height = Math.round(config.HEIGHT * height_100 / 100); + } + } + + //if only 1 dimension was provided + if (isNaN(width) || isNaN(height)) { + var ratio = config.WIDTH / config.HEIGHT; + if (isNaN(width)) + width = Math.round(height * ratio); + if (isNaN(height)) + height = Math.round(width / ratio); + } + + return [ + new app.Actions.Prepare_canvas_action('undo'), + new app.Actions.Update_config_action({ + WIDTH: parseInt(width), + HEIGHT: parseInt(height) + }), + new app.Actions.Prepare_canvas_action('do') + ]; + } + +} + +export default Image_resize_class; \ No newline at end of file diff --git a/frontend/src/js/modules/image/rotate.js b/frontend/src/js/modules/image/rotate.js new file mode 100644 index 0000000..ad6f35f --- /dev/null +++ b/frontend/src/js/modules/image/rotate.js @@ -0,0 +1,180 @@ +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Base_gui_class from './../../core/base-gui.js'; +import Helper_class from './../../libs/helpers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; +import app from '../../app.js'; + +var instance = null; + +class Image_rotate_class { + + constructor() { + //singleton + if (instance) { + return instance; + } + instance = this; + + this.Base_layers = new Base_layers_class(); + this.Base_gui = new Base_gui_class(); + this.Helper = new Helper_class(); + this.Dialog = new Dialog_class(); + + this.set_events(); + } + + set_events() { + document.addEventListener('keydown', (event) => { + var code = event.keyCode; + if (this.Helper.is_input(event.target)) + return; + + if (code == 76) { + //L - rotate left + this.left(); + event.preventDefault(); + } + }, false); + } + + rotate() { + var _this = this; + + if (config.layer.rotate === null) { + alertify.error('Rotate is not supported on this type of object. Convert to raster?'); + return; + } + + var angles = ['Custom', '0', '90', '180', '270']; + var initial_angle = config.layer.rotate; + + var settings = { + title: 'Rotate', + params: [ + {name: "rotate", title: "Rotate:", value: config.layer.rotate, range: [0, 360]}, + {name: "right_angle", title: "Right angle:", values: angles}, + ], + on_change: function (params, canvas_preview, w, h) { + _this.rotate_handler(params, false); + }, + on_finish: function (params) { + config.layer.rotate = initial_angle; + _this.rotate_handler(params); + }, + on_cancel: function (params) { + config.layer.rotate = initial_angle; + config.need_render = true; + }, + }; + this.Dialog.show(settings); + } + + rotate_handler(data, can_resize = true) { + var value = parseInt(data.rotate); + if (data.right_angle != 'Custom') { + value = parseInt(data.right_angle); + } + + if (value < 0) + value = 360 + value; + if (value >= 360) + value = value - 360; + let new_rotate = value; + + if (can_resize == true) { + app.State.do_action( + new app.Actions.Bundle_action('rotate_layer', 'Rotate Layer', [ + new app.Actions.Update_layer_action(config.layer.id, { + rotate: new_rotate + }), + ...this.check_sizes(new_rotate) + ]) + ); + } else { + config.layer.rotate = new_rotate; + config.need_render = true; + } + } + + left() { + let new_rotate = config.layer.rotate; + new_rotate -= 90; + if (new_rotate < 0) + new_rotate = 360 + new_rotate; + + app.State.do_action( + new app.Actions.Bundle_action('rotate_layer', 'Rotate Layer', [ + new app.Actions.Update_layer_action(config.layer.id, { + rotate: new_rotate + }), + ...this.check_sizes(new_rotate) + ]) + ); + } + + right() { + let new_rotate = config.layer.rotate; + new_rotate += 90; + if (new_rotate >= 360) + new_rotate = new_rotate - 360; + + app.State.do_action( + new app.Actions.Bundle_action('rotate_layer', 'Rotate Layer', [ + new app.Actions.Update_layer_action(config.layer.id, { + rotate: new_rotate + }), + ...this.check_sizes(new_rotate) + ]) + ); + } + + /** + * Makes sure image fits all after rotation + * @returns {array} actions to perform + */ + check_sizes(new_rotate) { + let actions = []; + var w = config.layer.width; + var h = config.layer.height; + + var o = new_rotate * Math.PI / 180; + var new_x = w * Math.abs(Math.cos(o)) + h * Math.abs(Math.sin(o)); + var new_y = w * Math.abs(Math.sin(o)) + h * Math.abs(Math.cos(o)); + + //round values + new_x = Math.ceil(Math.round(new_x * 1000) / 1000); + new_y = Math.ceil(Math.round(new_y * 1000) / 1000); + + if (new_x > config.WIDTH || new_y > config.HEIGHT) { + var dx = 0; + var dy = 0; + let new_width = config.WIDTH; + let new_height = config.HEIGHT; + if (new_x > config.WIDTH) { + dx = Math.ceil(new_x - new_width) / 2; + new_width = new_x; + } + if (new_y > config.HEIGHT) { + dy = Math.ceil(new_y - new_height) / 2; + new_height = new_y; + } + actions.push( + new app.Actions.Prepare_canvas_action('undo'), + new app.Actions.Update_layer_action(config.layer.id, { + x: config.layer.x + dx, + y: config.layer.y + dy + }), + new app.Actions.Update_config_action({ + WIDTH: new_width, + HEIGHT: new_height + }), + new app.Actions.Prepare_canvas_action('do') + ); + } + return actions; + } +} + +export default Image_rotate_class; \ No newline at end of file diff --git a/frontend/src/js/modules/image/size.js b/frontend/src/js/modules/image/size.js new file mode 100644 index 0000000..3836fb4 --- /dev/null +++ b/frontend/src/js/modules/image/size.js @@ -0,0 +1,140 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_gui_class from './../../core/base-gui.js'; +import Dialog_class from './../../libs/popup.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; +import Tools_settings_class from './../tools/settings.js'; +import Helper_class from './../../libs/helpers.js'; + +class Image_size_class { + + constructor() { + this.Base_gui = new Base_gui_class(); + this.POP = new Dialog_class(); + this.Tools_settings = new Tools_settings_class(); + this.Helper = new Helper_class(); + } + + size() { + var _this = this; + var common_dimensions = this.Base_gui.common_dimensions; + var units = this.Tools_settings.get_setting('default_units'); + var resolution = this.Tools_settings.get_setting('resolution'); + var enable_autoresize = this.Tools_settings.get_setting('enable_autoresize'); + + var resolutions = ['Custom']; + for (var i in common_dimensions) { + var value = common_dimensions[i]; + resolutions.push(value[0] + 'x' + value[1] + ' - ' + value[2]); + } + + //convert units + var width = this.Helper.get_user_unit(config.WIDTH, units, resolution); + var height = this.Helper.get_user_unit(config.HEIGHT, units, resolution); + + var settings = { + title: 'Canvas Size', + params: [ + {name: "w", title: "Width:", value: width, placeholder: width, comment: units}, + {name: "h", title: "Height:", value: height, placeholder: height, comment: units}, + {name: "resolution", title: "Resolution:", values: resolutions}, + {name: "layout", title: "Layout:", value: "Custom", values: ["Custom", "Landscape", "Portrait"]}, + {name: "enable_autoresize", title: "Enable autoresize:", value: enable_autoresize}, + {name: "in_proportion", title: "In proportion:", value: false}, + ], + on_finish: function (params) { + _this.size_handler(params); + }, + }; + this.POP.show(settings); + } + + size_handler(data) { + var width = parseFloat(data.w); + var height = parseFloat(data.h); + var ratio = config.WIDTH / config.HEIGHT; + var units = this.Tools_settings.get_setting('default_units'); + var resolution = this.Tools_settings.get_setting('resolution'); + + if (width < 0){ + width = 1; + } + if (height < 0){ + height = 1; + } + + this.Tools_settings.save_setting('enable_autoresize', data.enable_autoresize); + + //aspect ratio + if (isNaN(width) && isNaN(height)){ + alertify.error('Wrong dimensions'); + return; + } + if (isNaN(width)){ + width = height * ratio; + } + if (isNaN(height)){ + height = width / ratio; + } + + if (data.resolution != 'Custom') { + var dim = data.resolution.split(" "); + dim = dim[0].split("x"); + width = parseInt(dim[0]); + height = parseInt(dim[1]); + + if(data.layout == 'Portrait'){ + var tmp = width; + width = height; + height = tmp; + } + } + else{ + //convert units + width = this.Helper.get_internal_unit(width, units, resolution); + height = this.Helper.get_internal_unit(height, units, resolution); + } + + var actions = [ + new app.Actions.Prepare_canvas_action('undo'), + new app.Actions.Update_config_action({ + WIDTH: parseInt(width), + HEIGHT: parseInt(height) + }), + ]; + + if(data.in_proportion == true) { + //resize object and change coordinates + var width_ratio = config.WIDTH / width; + var height_ratio = config.HEIGHT / height; + var ratio = Math.max(width_ratio, height_ratio); + + for (var i in config.layers) { + var layer = config.layers[i]; + if(layer.x != null && layer.y != null) { + var data_new = { + x: Math.round(layer.x / width_ratio), + y: Math.round(layer.y / height_ratio), + }; + actions.push(new app.Actions.Update_layer_action(layer.id, data_new)); + } + if(layer.width != null && layer.height != null) { + var data_new = { + width: Math.round(layer.width / ratio), + height: Math.round(layer.height / ratio), + }; + actions.push(new app.Actions.Update_layer_action(layer.id, data_new)); + } + } + } + + actions.push(new app.Actions.Prepare_canvas_action('do')); + + //execute + app.State.do_action( + new app.Actions.Bundle_action('set_image_size', 'Set Image Size', actions) + ); + } +} + +export default Image_size_class; diff --git a/frontend/src/js/modules/image/translate.js b/frontend/src/js/modules/image/translate.js new file mode 100644 index 0000000..4db6bf4 --- /dev/null +++ b/frontend/src/js/modules/image/translate.js @@ -0,0 +1,47 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Tools_settings_class from './../tools/settings.js'; +import Helper_class from './../../libs/helpers.js'; + +class Image_translate_class { + + constructor() { + this.POP = new Dialog_class(); + this.Tools_settings = new Tools_settings_class(); + this.Helper = new Helper_class(); + } + + translate() { + var _this = this; + var units = this.Tools_settings.get_setting('default_units'); + var resolution = this.Tools_settings.get_setting('resolution'); + + var pos_x = this.Helper.get_user_unit(config.layer.x, units, resolution); + var pos_y = this.Helper.get_user_unit(config.layer.y, units, resolution); + + var settings = { + title: 'Translate', + params: [ + {name: "x", title: "X position:", value: pos_x}, + {name: "y", title: "Y position:", value: pos_y}, + ], + on_finish: function (params) { + var pos_x = _this.Helper.get_internal_unit(params.x, units, resolution); + var pos_y = _this.Helper.get_internal_unit(params.y, units, resolution); + + app.State.do_action( + new app.Actions.Bundle_action('translate_layer', 'Translate Layer', [ + new app.Actions.Update_layer_action(config.layer.id, { + x: pos_x, + y: pos_y, + }) + ]) + ); + }, + }; + this.POP.show(settings); + } +} + +export default Image_translate_class; diff --git a/frontend/src/js/modules/image/trim.js b/frontend/src/js/modules/image/trim.js new file mode 100644 index 0000000..23f6113 --- /dev/null +++ b/frontend/src/js/modules/image/trim.js @@ -0,0 +1,305 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_gui_class from './../../core/base-gui.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Dialog_class from './../../libs/popup.js'; +import Helper_class from './../../libs/helpers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +var instance = null; + +class Image_trim_class { + + constructor() { + //singleton + if (instance) { + return instance; + } + instance = this; + + this.Base_layers = new Base_layers_class(); + this.Base_gui = new Base_gui_class(); + this.Helper = new Helper_class(); + this.Dialog = new Dialog_class(); + + this.set_events(); + } + + set_events() { + document.addEventListener('keydown', (event) => { + var code = event.keyCode; + if (this.Helper.is_input(event.target)) + return; + + if (code == 84) { + //trim + this.trim(); + event.preventDefault(); + } + }, false); + } + + trim() { + var _this = this; + var removeWhiteColor = false; + if(config.TRANSPARENCY == false) + removeWhiteColor = true; + + var settings = { + title: 'Trim', + params: [ + {name: "trim_layer", title: "Trim layer:", value: true}, + {name: "trim_all", title: "Trim borders:", value: true}, + {name: "power", title: "Power:", value: 0, max: 255}, + {name: "remove_white", title: "Trim white color?", value: removeWhiteColor}, + ], + on_finish: async (params) => { + if (params.trim_layer == true) { + //first trim + let actions = []; + actions = actions.concat(this.trim_layer(config.layer.id, params.remove_white, params.power)); + await app.State.do_action( + new app.Actions.Bundle_action('trim_layers', 'Trim Layers', actions) + ); + } + if (params.trim_all == true) { + //second trim + let actions = []; + actions = actions.concat(_this.trim_all(params.remove_white, params.power)); + app.State.do_action( + new app.Actions.Bundle_action('trim_layers', 'Trim Layers', actions) + ); + } + }, + }; + this.Dialog.show(settings); + } + + /** + * removes empty (white/transparent) area from top, right, bottom and left sides + * This affects layer data + * + * @param layer_id + * @param removeWhiteColor + * @param {int} power + */ + trim_layer(layer_id, removeWhiteColor = false, power = 0) { + var layer = this.Base_layers.get_layer(layer_id); + + if (layer.type != 'image') { + alertify.error('Skip - layer must be image.'); + return false; + } + + var trim = this.get_trim_info(layer_id, removeWhiteColor, power); + trim = trim.relative; + + //if image was stretched + var width_ratio = (layer.width / layer.width_original); + var height_ratio = (layer.height / layer.height_original); + + //create smaller canvas + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext("2d"); + canvas.width = trim.width / width_ratio; + canvas.height = trim.height / height_ratio; + + //cut required part + ctx.translate(-trim.left / width_ratio, -trim.top / height_ratio); + canvas.getContext("2d").drawImage(layer.link, 0, 0); + ctx.translate(0, 0); + + return [ + new app.Actions.Update_layer_image_action(canvas, layer.id), + new app.Actions.Update_layer_action(layer.id, { + x: layer.x + trim.left, + y: layer.y + trim.top, + width: Math.ceil(canvas.width * width_ratio), + height: Math.ceil(canvas.height * height_ratio), + width_original: canvas.width, + height_original: canvas.height + }) + ]; + } + + /** + * change canvas size, so there is no empty (white/transparent) areas on top, right, bottom and left sides + * this affect canvas size and all layers positions + * + * @param removeWhiteColor + * @param {int} power + */ + trim_all(removeWhiteColor = false, power = 0) { + let actions = []; + + var all_top = config.HEIGHT; + var all_left = config.WIDTH; + var all_bottom = config.HEIGHT; + var all_right = config.WIDTH; + + if (removeWhiteColor == undefined) { + removeWhiteColor = false; + if (config.TRANSPARENCY == false) { + removeWhiteColor = true; + } + } + + //collect info + for (let i = 0; i < config.layers.length; i++) { + let layer = config.layers[i]; + + if (layer.width == null || layer.height == null || layer.x == null || layer.y == null) { + //layer without dimensions + const trim_info = this.get_trim_info(layer.id, removeWhiteColor, power); + + all_top = Math.min(all_top, trim_info.top); + all_left = Math.min(all_left, trim_info.left); + all_bottom = Math.min(all_bottom, trim_info.bottom); + all_right = Math.min(all_right, trim_info.right); + } + else{ + all_top = Math.min(all_top, layer.y); + all_left = Math.min(all_left, layer.x); + all_bottom = Math.min(all_bottom, config.HEIGHT - layer.height - layer.y); + all_right = Math.min(all_right, config.WIDTH - layer.width - layer.x); + } + } + + //move every layer + for (let i = 0; i < config.layers.length; i++) { + let layer = config.layers[i]; + if (layer.x == null || layer.y == null || layer.type == null) + continue; + + actions.push( + new app.Actions.Update_layer_action(layer.id, { + x: layer.x - all_left, + y: layer.y - all_top + }) + ); + } + + //resize + actions.push( + new app.Actions.Prepare_canvas_action('undo'), + new app.Actions.Update_config_action({ + WIDTH: Math.max(1, config.WIDTH - all_left - all_right), + HEIGHT: Math.max(1, config.HEIGHT - all_top - all_bottom) + }), + new app.Actions.Prepare_canvas_action('do') + ); + return actions; + } + + /** + * get painted area coords + * + * @param {int} layer_id + * @param {boolean} trim_white + * @param {int} power + * @returns {object} keys: top, left, bottom, right, width, height, relative + */ + get_trim_info(layer_id, trim_white, power) { + if (trim_white == undefined) { + trim_white = false; + if (config.TRANSPARENCY == false) { + trim_white = true; + } + } + if (power == undefined) { + power = 0; + } + var layer = this.Base_layers.get_layer(layer_id); + + var canvas = this.Base_layers.convert_layer_to_canvas(layer_id, null, false); + var ctx = canvas.getContext("2d"); + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var imgData = img.data; + + var top = 0; + var left = 0; + var bottom = 0; + var right = 0; + + //check top + main1: + for (var y = 0; y < img.height; y++) { + for (var x = 0; x < img.width; x++) { + var k = ((y * (img.width * 4)) + (x * 4)); + if (imgData[k + 3] <= power) + continue; //transparent + if (trim_white == true && imgData[k] >= 255 - power && imgData[k + 1] >= 255 - power + && imgData[k + 2] >= 255 - power) + continue; //white + break main1; + } + top++; + } + //check left + main2: + for (var x = 0; x < img.width; x++) { + for (var y = 0; y < img.height; y++) { + var k = ((y * (img.width * 4)) + (x * 4)); + if (imgData[k + 3] <= power) + continue; //transparent + if (trim_white == true && imgData[k] >= 255 - power && imgData[k + 1] >= 255 - power + && imgData[k + 2] >= 255 - power) + continue; //white + break main2; + } + left++; + } + //check bottom + main3: + for (var y = img.height - 1; y >= 0; y--) { + for (var x = img.width - 1; x >= 0; x--) { + var k = ((y * (img.width * 4)) + (x * 4)); + if (imgData[k + 3] <= power) + continue; //transparent + if (trim_white == true && imgData[k] >= 255 - power && imgData[k + 1] >= 255 - power + && imgData[k + 2] >= 255 - power) + continue; //white + break main3; + } + bottom++; + } + //check right + main4: + for (var x = img.width - 1; x >= 0; x--) { + for (var y = img.height - 1; y >= 0; y--) { + var k = ((y * (img.width * 4)) + (x * 4)); + if (imgData[k + 3] <= power) + continue; //transparent + if (trim_white == true && imgData[k] >= 255 - power && imgData[k + 1] >= 255 - power + && imgData[k + 2] >= 255 - power) + continue; //white + break main4; + } + right++; + } + + var top_rel = top - layer.y; + var left_rel = left - layer.x; + var bottom_rel = bottom - (config.HEIGHT - layer.y - layer.height); + var right_rel = right - (config.WIDTH - layer.x - layer.width); + + return { + top: top, + left: left, + bottom: bottom, + right: right, + width: canvas.width - left - right, + height: canvas.height - top - bottom, + relative: { + top: top_rel, + left: left_rel, + bottom: bottom_rel, + right: right_rel, + width: canvas.width - left - right, + height: canvas.height - top - bottom, + }, + }; + } +} + +export default Image_trim_class; diff --git a/frontend/src/js/modules/layer/clear.js b/frontend/src/js/modules/layer/clear.js new file mode 100644 index 0000000..dbc758c --- /dev/null +++ b/frontend/src/js/modules/layer/clear.js @@ -0,0 +1,19 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Layer_clear_class { + + constructor() { + this.Base_layers = new Base_layers_class(); + } + + clear() { + return app.State.do_action( + new app.Actions.Clear_layer_action(config.layer.id) + ); + } + +} + +export default Layer_clear_class; \ No newline at end of file diff --git a/frontend/src/js/modules/layer/composition.js b/frontend/src/js/modules/layer/composition.js new file mode 100644 index 0000000..c031e4c --- /dev/null +++ b/frontend/src/js/modules/layer/composition.js @@ -0,0 +1,86 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Base_gui_class from "../../core/base-gui.js"; + +class Layer_composition_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_gui_class = new Base_gui_class(); + } + + composition() { + var compositions = [ + "-- Default --", + "color", + "color-burn", + "color-dodge", + "copy", + "darken", + "darker", + "destination-atop", + "destination-in", + "destination-out", + "destination-over", + "difference", + "exclusion", + "hard-light", + "hue", + "lighten", + "lighter", + "luminosity", + "multiply", + "overlay", + "saturation", + "screen", + "soft-light", + "source-atop", + "source-in", + "source-out", + "source-over", + "xor", + ]; + + var initial_composition = config.layer.composition; + var _this = this; + + var settings = { + title: 'Composition', + //preview: true, + params: [ + {name: "composition", title: "Composition:", value: config.layer.composition, values: compositions}, + ], + on_change: function (params, canvas_preview, w, h) { + //redraw preview + if (params.composition == '-- Default --') { + params.composition = 'source-over'; + } + config.layer.composition = params.composition; + config.need_render = true; + _this.Base_gui_class.GUI_layers.render_layers(); + }, + on_finish: function (params) { + config.layer.composition = initial_composition; + if (params.composition == '-- Default --') { + params.composition = 'source-over'; + } + app.State.do_action( + new app.Actions.Bundle_action('change_composition', 'Change Composition', [ + new app.Actions.Update_layer_action(config.layer.id, { + composition: params.composition + }) + ]) + ); + }, + on_cancel: function (params) { + config.layer.composition = initial_composition; + config.need_render = true; + _this.Base_gui_class.GUI_layers.render_layers(); + }, + }; + this.POP.show(settings); + } +} + +export default Layer_composition_class; diff --git a/frontend/src/js/modules/layer/delete.js b/frontend/src/js/modules/layer/delete.js new file mode 100644 index 0000000..c4a444f --- /dev/null +++ b/frontend/src/js/modules/layer/delete.js @@ -0,0 +1,19 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Layer_delete_class { + + constructor() { + this.Base_layers = new Base_layers_class(); + } + + delete() { + app.State.do_action( + new app.Actions.Delete_layer_action(config.layer.id) + ); + } + +} + +export default Layer_delete_class; \ No newline at end of file diff --git a/frontend/src/js/modules/layer/differences.js b/frontend/src/js/modules/layer/differences.js new file mode 100644 index 0000000..755808d --- /dev/null +++ b/frontend/src/js/modules/layer/differences.js @@ -0,0 +1,105 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Dialog_class from './../../libs/popup.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Layer_differences_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + differences() { + var _this = this; + if (this.Base_layers.find_previous(config.layer.id) == null) { + alertify.error('There are no layers behind.'); + return false; + } + + var settings = { + title: 'Differences', + preview: true, + params: [ + {name: "sensitivity", title: "Sensitivity:", value: "0", range: [0, 255]}, + ], + on_change: function (params, canvas_preview, w, h) { + _this.calc_differences(params.sensitivity, canvas_preview, w, h); + }, + on_finish: function (params) { + _this.calc_differences(params.sensitivity); + }, + }; + this.POP.show(settings); + } + + calc_differences(sensitivity, canvas_preview, w, h) { + //create tmp canvas + var canvas = document.createElement('canvas'); + canvas.width = config.WIDTH; + canvas.height = config.HEIGHT; + var ctx = canvas.getContext("2d"); + + //get source data + this.Base_layers.render_object(ctx, config.layer); + var imgData1 = ctx.getImageData(0, 0, config.WIDTH, config.HEIGHT).data; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + //get target data + var next_layer = this.Base_layers.find_previous(config.layer.id); + this.Base_layers.render_object(ctx, next_layer); + var imgData2 = ctx.getImageData(0, 0, config.WIDTH, config.HEIGHT).data; + + //prepare background + ctx.rect(0, 0, config.WIDTH, config.HEIGHT); + ctx.fillStyle = "#ffffff"; + ctx.fill(); + + //generate diff + var img3 = ctx.getImageData(0, 0, config.WIDTH, config.HEIGHT); + var imgData3 = img3.data; + for (var xx = 0; xx < config.WIDTH; xx++) { + for (var yy = 0; yy < config.HEIGHT; yy++) { + var x = (xx + yy * config.WIDTH) * 4; + + if (Math.abs(imgData1[x] - imgData2[x]) > sensitivity + || Math.abs(imgData1[x + 1] - imgData2[x + 1]) > sensitivity + || Math.abs(imgData1[x + 2] - imgData2[x + 2]) > sensitivity + || Math.abs(imgData1[x + 3] - imgData2[x + 3]) > sensitivity) { + imgData3[x] = 255; + imgData3[x + 1] = 0; + imgData3[x + 2] = 0; + imgData3[x + 3] = 255; + } + } + } + ctx.putImageData(img3, 0, 0); + + //show + if (canvas_preview == undefined) { + //main + var params = []; + params.type = 'image'; + params.name = 'Differences'; + params.data = canvas.toDataURL("image/png"); + app.State.do_action( + new app.Actions.Insert_layer_action(params) + ); + } + else { + //preview + canvas_preview.save(); + canvas_preview.scale(w / config.WIDTH, h / config.HEIGHT); + canvas_preview.drawImage(canvas, 0, 0); + canvas_preview.restore(); + } + + canvas.width = 1; + canvas.height = 1; + } + +} + +export default Layer_differences_class; \ No newline at end of file diff --git a/frontend/src/js/modules/layer/duplicate.js b/frontend/src/js/modules/layer/duplicate.js new file mode 100644 index 0000000..59876b1 --- /dev/null +++ b/frontend/src/js/modules/layer/duplicate.js @@ -0,0 +1,78 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Helper_class from './../../libs/helpers.js'; + +var instance = null; + +class Layer_duplicate_class { + + constructor() { + //singleton + if (instance) { + return instance; + } + instance = this; + + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + + this.set_events(); + } + + set_events() { + document.addEventListener('keydown', (event) => { + var code = event.keyCode; + if (this.Helper.is_input(event.target)) + return; + + if (code == 68) { + //D - duplicate + this.duplicate(); + event.preventDefault(); + } + }, false); + } + + duplicate() { + var params = JSON.parse(JSON.stringify(config.layer)); + delete params.id; + delete params.order; + + //generate name + var name_number = params.name.match(/^(.*) #([0-9]+)$/); + if(name_number == null){ + //first duplicate + params.name = params.name + " #2"; + } + else{ + //nth duplicate - name like "query #17" + params.name = name_number[1] + " #" + (parseInt(name_number[2]) + 1) + } + + if(params.x != 0 || params.y != 0 || params.width != config.WIDTH || params.height != config.HEIGHT){ + params.x += 10; + params.y += 10; + } + + for (var i in params) { + //remove private attributes + if (i[0] == '_') + delete params[i]; + } + + if (params.type == 'image') { + //image + params.link = config.layer.link.cloneNode(true); + } + + app.State.do_action( + new app.Actions.Bundle_action('duplicate_layer', 'Duplicate Layer', [ + new app.Actions.Insert_layer_action(params) + ]) + ); + } + +} + +export default Layer_duplicate_class; \ No newline at end of file diff --git a/frontend/src/js/modules/layer/flatten.js b/frontend/src/js/modules/layer/flatten.js new file mode 100644 index 0000000..cf148f3 --- /dev/null +++ b/frontend/src/js/modules/layer/flatten.js @@ -0,0 +1,56 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Layer_flatten_class { + + constructor() { + this.Base_layers = new Base_layers_class(); + } + + flatten() { + //create tmp canvas + var canvas = document.createElement('canvas'); + canvas.width = config.WIDTH; + canvas.height = config.HEIGHT; + var ctx = canvas.getContext("2d"); + + var layers_sorted = this.Base_layers.get_sorted_layers(); + + //paint layers + for (var i = layers_sorted.length - 1; i >= 0; i--) { + var layer = layers_sorted[i]; + + ctx.globalAlpha = layer.opacity / 100; + ctx.globalCompositeOperation = layer.composition; + + this.Base_layers.render_object(ctx, layer); + } + + //create requested layer + var params = []; + params.type = 'image'; + params.name = 'Merged'; + params.data = canvas.toDataURL("image/png"); + + //remove rest of layers + let delete_actions = []; + for (var i = config.layers.length - 1; i >= 0; i--) { + delete_actions.push(new app.Actions.Delete_layer_action(config.layers[i].id)); + } + // Run actions + app.State.do_action( + new app.Actions.Bundle_action('flatten_image', 'Flatten Image', [ + new app.Actions.Insert_layer_action(params), + ...delete_actions + ]) + ); + + canvas.width = 1; + canvas.height = 1; + } + +} + +export default Layer_flatten_class; \ No newline at end of file diff --git a/frontend/src/js/modules/layer/merge.js b/frontend/src/js/modules/layer/merge.js new file mode 100644 index 0000000..82f093e --- /dev/null +++ b/frontend/src/js/modules/layer/merge.js @@ -0,0 +1,59 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Layer_merge_class { + + constructor() { + this.Base_layers = new Base_layers_class(); + } + + merge() { + if (this.Base_layers.find_previous(config.layer.id) == null) { + alertify.error('There are no layers behind.'); + return false; + } + + //create tmp canvas + var canvas = document.createElement('canvas'); + canvas.width = config.WIDTH; + canvas.height = config.HEIGHT; + var ctx = canvas.getContext("2d"); + + //first layer + var previous_layer = this.Base_layers.find_previous(config.layer.id); + var previous_id = previous_layer.id; + ctx.globalAlpha = previous_layer.opacity / 100; + ctx.globalCompositeOperation = previous_layer.composition; + this.Base_layers.render_object(ctx, previous_layer); + + //second layer + var current_id = config.layer.id; + var current_order = config.layer.order; + ctx.globalAlpha = config.layer.opacity / 100; + ctx.globalCompositeOperation = config.layer.composition; + this.Base_layers.render_object(ctx, config.layer); + + //create requested layer + var params = []; + params.type = 'image'; + params.name = config.layer.name + ' + merged'; + params.order = current_order; + params.data = canvas.toDataURL("image/png"); + app.State.do_action( + new app.Actions.Bundle_action('merge_layers', 'Merge Layers', [ + new app.Actions.Insert_layer_action(params), + new app.Actions.Delete_layer_action(current_id), + new app.Actions.Delete_layer_action(previous_id) + ]) + ); + + //free canvas data + canvas.width = 1; + canvas.height = 1; + } + +} + +export default Layer_merge_class; \ No newline at end of file diff --git a/frontend/src/js/modules/layer/move.js b/frontend/src/js/modules/layer/move.js new file mode 100644 index 0000000..f27e3a0 --- /dev/null +++ b/frontend/src/js/modules/layer/move.js @@ -0,0 +1,24 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Layer_move_class { + + constructor() { + this.Base_layers = new Base_layers_class(); + } + + up() { + app.State.do_action( + new app.Actions.Reorder_layer_action(config.layer.id, 1) + ); + } + + down() { + app.State.do_action( + new app.Actions.Reorder_layer_action(config.layer.id, -1) + ); + } +} + +export default Layer_move_class; diff --git a/frontend/src/js/modules/layer/new.js b/frontend/src/js/modules/layer/new.js new file mode 100644 index 0000000..54f0207 --- /dev/null +++ b/frontend/src/js/modules/layer/new.js @@ -0,0 +1,97 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import GUI_tools_class from './../../core/gui/gui-tools.js'; +import Base_selection_class from './../../core/base-selection.js'; +import Selection_class from './../../tools/selection.js'; +import Helper_class from './../../libs/helpers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Layer_new_class { + + constructor() { + this.Base_layers = new Base_layers_class(); + this.Selection = new Selection_class(); + this.Base_selection = new Base_selection_class(this.Base_layers.ctx); + this.GUI_tools = new GUI_tools_class(); + this.Helper = new Helper_class(); + + this.set_events(); + } + + set_events() { + document.addEventListener('keydown', (event) => { + var code = event.keyCode; + if (this.Helper.is_input(event.target)) + return; + + if (code == 78 && event.ctrlKey != true && event.metaKey != true) { + //N + this.new(); + } + }, false); + } + + new() { + app.State.do_action( + new app.Actions.Insert_layer_action() + ); + } + + new_selection() { + var selection = this.Base_selection.get_selection(); + var layer = config.layer; + + if (selection.width === null || config.layer.type != 'image') { + alertify.error('Empty selection or type not image.'); + return; + } + if (config.TOOL.name != 'selection') { + alertify.error('Empty selection or type not image.'); + return; + } + + //if image was stretched + var width_ratio = (layer.width / layer.width_original); + var height_ratio = (layer.height / layer.height_original); + + var left = selection.x - layer.x; + var top = selection.y - layer.y; + + //adapt to origin size + selection.width = selection.width / width_ratio; + selection.height = selection.height / height_ratio; + + //create new layer + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext("2d"); + canvas.width = Math.round(selection.width); + canvas.height = Math.round(selection.height); + + ctx.translate(-left / width_ratio, -top / height_ratio); + ctx.drawImage(config.layer.link, 0, 0); + ctx.translate(0, 0); + + //register it + var params = { + x: Math.round(selection.x), + y: Math.round(selection.y), + width: Math.round(selection.width * width_ratio), + height: Math.round(selection.height * height_ratio), + width_original: Math.round(selection.width), + height_original: Math.round(selection.height), + type: 'image', + data: canvas.toDataURL("image/png"), + }; + app.State.do_action( + new app.Actions.Bundle_action('new_layer', 'New Layer', [ + new app.Actions.Insert_layer_action(params, false), + ...this.Selection.on_leave(), + new app.Actions.Activate_tool_action('select') + ]) + ); + } + +} + +export default Layer_new_class; \ No newline at end of file diff --git a/frontend/src/js/modules/layer/raster.js b/frontend/src/js/modules/layer/raster.js new file mode 100644 index 0000000..05f0ef4 --- /dev/null +++ b/frontend/src/js/modules/layer/raster.js @@ -0,0 +1,38 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Layer_raster_class { + + constructor() { + this.Base_layers = new Base_layers_class(); + } + + raster() { + var canvas = this.Base_layers.convert_layer_to_canvas(); + var current_layer = config.layer; + var current_id = current_layer.id; + + //show + var params = { + type: 'image', + name: config.layer.name + ' + raster', + data: canvas.toDataURL("image/png"), + x: parseInt(canvas.dataset.x), + y: parseInt(canvas.dataset.y), + width: canvas.width, + height: canvas.height, + opacity: current_layer.opacity, + }; + app.State.do_action( + new app.Actions.Bundle_action('convert_to_raster', 'Convert to Raster', [ + new app.Actions.Insert_layer_action(params, false), + new app.Actions.Delete_layer_action(current_id) + ]) + ); + } + +} + +export default Layer_raster_class; diff --git a/frontend/src/js/modules/layer/rename.js b/frontend/src/js/modules/layer/rename.js new file mode 100644 index 0000000..0d8f217 --- /dev/null +++ b/frontend/src/js/modules/layer/rename.js @@ -0,0 +1,55 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Dialog_class from './../../libs/popup.js'; +import Helper_class from './../../libs/helpers.js'; + +class Layer_rename_class { + + constructor() { + this.Base_layers = new Base_layers_class(); + this.POP = new Dialog_class(); + this.Helper = new Helper_class(); + } + + rename(id = null) { + var _this = this; + + var name_ = this.Helper.escapeHtml(config.layer.name); + + var settings = { + title: 'Rename', + params: [ + {name: "name", title: "Name:", value: name_}, + ], + on_load: function () { + document.querySelector('#pop_data_name').select(); + }, + on_finish: function (params) { + app.State.do_action( + new app.Actions.Bundle_action('rename_layer', 'Rename Layer', [ + new app.Actions.Refresh_layers_gui_action('undo'), + new app.Actions.Update_layer_action(id || config.layer.id, { + name: _this.validate_name(params.name) + }), + new app.Actions.Refresh_layers_gui_action('do') + ]) + ); + }, + }; + this.POP.show(settings); + } + + validate_name(text) { + text = text + .replace(/&/g, "-") + .replace(//g, "-") + .replace(/"/g, "-") + .replace(/'/g, "-"); + + return text; + } +} + +export default Layer_rename_class; diff --git a/frontend/src/js/modules/layer/visibility.js b/frontend/src/js/modules/layer/visibility.js new file mode 100644 index 0000000..8e3017c --- /dev/null +++ b/frontend/src/js/modules/layer/visibility.js @@ -0,0 +1,19 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Layer_visibility_class { + + constructor() { + this.Base_layers = new Base_layers_class(); + } + + toggle() { + app.State.do_action( + new app.Actions.Toggle_layer_visibility_action(config.layer.id) + ); + } + +} + +export default Layer_visibility_class; \ No newline at end of file diff --git a/frontend/src/js/modules/tools/color_to_alpha.js b/frontend/src/js/modules/tools/color_to_alpha.js new file mode 100644 index 0000000..09070ee --- /dev/null +++ b/frontend/src/js/modules/tools/color_to_alpha.js @@ -0,0 +1,82 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Dialog_class from './../../libs/popup.js'; +import Helper_class from './../../libs/helpers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Tools_colorToAlpha_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + } + + color_to_alpha() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Color to Alpha', + preview: true, + on_change: function (params, canvas_preview, w, h) { + var img = canvas_preview.getImageData(0, 0, w, h); + var data = _this.change(img, params.color); + canvas_preview.putImageData(data, 0, 0); + }, + params: [ + {name: "color", title: "Color:", value: config.COLOR, type: 'color'}, + ], + on_finish: function (params) { + _this.apply_affect(params.color); + }, + }; + this.POP.show(settings); + } + + apply_affect(color) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.change(img, color); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(data, color) { + var imgData = data.data; + var back_color = this.Helper.hexToRgb(color); + + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + + //calculate difference from requested color, and change alpha + var diff = Math.abs(imgData[i] - back_color.r) + Math.abs(imgData[i + 1] - back_color.g) + Math.abs(imgData[i + 2] - back_color.b) / 3; + imgData[i + 3] = Math.round(diff); + + //combining 2 layers in future will change colors, so make changes to get same colors in final image + //color_result = color_1 * (alpha_1 / 255) * (1 - A2 / 255) + color_2 * (alpha_2 / 255) + //color_2 = (color_result - color_1 * (alpha_1 / 255) * (1 - A2 / 255)) / (alpha_2 / 255) + imgData[i] = Math.ceil((imgData[i] - back_color.r * (1 - imgData[i + 3] / 255)) / (imgData[i + 3] / 255)); + imgData[i + 1] = Math.ceil((imgData[i + 1] - back_color.g * (1 - imgData[i + 3] / 255)) / (imgData[i + 3] / 255)); + imgData[i + 2] = Math.ceil((imgData[i + 2] - back_color.b * (1 - imgData[i + 3] / 255)) / (imgData[i + 3] / 255)); + } + return data; + } + +} + +export default Tools_colorToAlpha_class; \ No newline at end of file diff --git a/frontend/src/js/modules/tools/color_zoom.js b/frontend/src/js/modules/tools/color_zoom.js new file mode 100644 index 0000000..6f92856 --- /dev/null +++ b/frontend/src/js/modules/tools/color_zoom.js @@ -0,0 +1,83 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Dialog_class from './../../libs/popup.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Tools_colorZoom_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + color_zoom() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Color zoom', + preview: true, + params: [ + {name: "zoom", title: "Zoom:", value: "2", range: [2, 20], }, + {name: "center", title: "Center:", value: "128", range: [0, 255]}, + ], + on_change: function (params, canvas_preview, w, h) { + var img = canvas_preview.getImageData(0, 0, w, h); + var data = _this.change(img, params.zoom, params.center); + canvas_preview.putImageData(data, 0, 0); + }, + on_finish: function (params) { + _this.save_zoom(params.zoom, params.center); + }, + }; + this.POP.show(settings); + } + + save_zoom(zoom, center) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.change(img, zoom, center); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + change(data, zoom, center) { + var imgData = data.data; + var grey; + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + + grey = Math.round(0.2126 * imgData[i] + 0.7152 * imgData[i + 1] + 0.0722 * imgData[i + 2]); + + for (var j = 0; j < 3; j++) { + var k = i + j; + if (grey > center) + imgData[k] += (imgData[k] - center) * zoom; + else if (grey < center) + imgData[k] -= (center - imgData[k]) * zoom; + if (imgData[k] < 0) + imgData[k] = 0; + if (imgData[k] > 255) + imgData[k] = 255; + } + } + return data; + } + +} + +export default Tools_colorZoom_class; \ No newline at end of file diff --git a/frontend/src/js/modules/tools/content_fill.js b/frontend/src/js/modules/tools/content_fill.js new file mode 100644 index 0000000..797a046 --- /dev/null +++ b/frontend/src/js/modules/tools/content_fill.js @@ -0,0 +1,266 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Dialog_class from './../../libs/popup.js'; +import ImageFilters from './../../libs/imagefilters.js'; +import Image_trim_class from './../image/trim.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Tools_contentFill_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Image_trim = new Image_trim_class(); + } + + content_fill() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + if (config.layer.x == 0 && config.layer.y == 0 && config.layer.width == config.WIDTH + && config.layer.height == config.HEIGHT) { + alertify.error('Can not use this tool on current layer: image already takes all area.'); + return; + } + + var settings = { + title: 'Content Fill', + preview: true, + on_change: function (params, canvas_preview, w, h, canvasElement) { + canvas_preview.clearRect(0, 0, w, h); + + //create tmp canvas + var canvas = document.createElement('canvas'); + canvas.width = config.WIDTH; + canvas.height = config.HEIGHT; + + //change data + _this.change(canvas, params); + + //add to preview + canvas_preview.drawImage(canvas, 0, 0, w, h); + }, + params: [ + {name: "mode", title: "Mode:", values: ['Expand edges', 'Cloned edges', 'Resized as background'], }, + {name: "blur_power", title: "Blur power:", value: 5, range: [1, 20]}, + {name: "blur_h", title: "Horizontal blur:", value: 5, range: [0, 30]}, + {name: "blur_v", title: "Vertical blur:", value: 5, range: [0, 30]}, + {name: "clone_count", title: "Clone count:", value: 15, range: [10, 50]}, + ], + on_finish: function (params) { + _this.apply_affect(params); + }, + }; + this.POP.show(settings); + } + + apply_affect(params) { + //create tmp canvas + var canvas = document.createElement('canvas'); + canvas.width = config.WIDTH; + canvas.height = config.HEIGHT; + + //change data + this.change(canvas, params); + + //save + return app.State.do_action( + new app.Actions.Bundle_action('content_fill', 'Content Fill', [ + new app.Actions.Update_layer_action(config.layer.id, { + x: 0, + y: 0, + width: config.WIDTH, + height: config.HEIGHT + }), + new app.Actions.Update_layer_image_action(canvas) + ]) + ); + } + + change(canvas, params) { + var ctx = canvas.getContext("2d"); + var mode = params.mode; + + //generate background + if (mode == 'Expand edges') + this.add_edge_background(canvas, params); + else if (mode == 'Resized as background') + this.add_resized_background(canvas, params); + else if (mode == 'Cloned edges') + this.add_cloned_background(canvas, params); + + //draw original image + this.Base_layers.render_object(ctx, config.layer); + } + + add_edge_background(canvas, params) { + var ctx = canvas.getContext("2d"); + var trim_info = this.Image_trim.get_trim_info(config.layer.id); + var original = this.Base_layers.convert_layer_to_canvas(); + + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(original, trim_info.left, trim_info.top); + + //draw top + ctx.drawImage(original, + 0, 0, original.width, 1, //source + trim_info.left, 0, original.width, trim_info.top); //target + + //bottom + ctx.drawImage(original, + 0, original.height - 1, original.width, 1, + trim_info.left, trim_info.top + original.height, original.width, canvas.height); + + //left + ctx.drawImage(original, + 0, 0, 1, original.height, + 0, trim_info.top, trim_info.left, original.height); + + //right + ctx.drawImage(original, + original.width - 1, 0, 1, original.height, + trim_info.left + original.width, trim_info.top, canvas.width, original.height); + + //fill corners + + //left top + ctx.drawImage(original, + 0, 0, 1, 1, + 0, 0, trim_info.left, trim_info.top); + + //right top + ctx.drawImage(original, + original.width - 1, 0, 1, 1, + trim_info.left + original.width, 0, canvas.width, trim_info.top); + + //left bottom + ctx.drawImage(original, + 0, original.height - 1, 1, 1, + 0, trim_info.top + original.height, trim_info.left, trim_info.bottom); + + //right bottom + ctx.drawImage(original, + original.width - 1, original.height - 1, 1, 1, + trim_info.left + original.width, trim_info.top + original.height, trim_info.right, trim_info.bottom); + + //add blur + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var blurred = ImageFilters.BoxBlur(img, params.blur_h, params.blur_v, params.blur_power); + ctx.putImageData(blurred, 0, 0); + } + + add_resized_background(canvas, params) { + var ctx = canvas.getContext("2d"); + + //draw original resized + var original = this.Base_layers.convert_layer_to_canvas(); + ctx.drawImage(original, 0, 0, canvas.width, canvas.height); + + //add blur + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var blurred = ImageFilters.BoxBlur(img, params.blur_h, params.blur_v, params.blur_power); + ctx.putImageData(blurred, 0, 0); + } + + add_cloned_background(canvas, params) { + var blocks = params.clone_count; + var ctx = canvas.getContext("2d"); + var trim_info = this.Image_trim.get_trim_info(config.layer.id); + var original = this.Base_layers.convert_layer_to_canvas(); + + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(original, trim_info.left, trim_info.top); + + //top + var bsize = Math.ceil(original.width / blocks); + for (var i = 0; i < original.width; i = i + bsize) { + for (var j = 0; j < trim_info.top; j = j + bsize) { + ctx.drawImage(original, + i, 0, bsize, bsize, + trim_info.left + i, 0 + j, bsize, bsize); + } + } + + //bottom + var bsize = Math.ceil(original.width / blocks); + for (var i = 0; i < original.width; i = i + bsize) { + for (var j = 0; j < canvas.height; j = j + bsize) { + ctx.drawImage(original, + i, original.height - bsize, bsize, bsize, + trim_info.left + i, trim_info.top + original.height + j, bsize, bsize); + } + } + + //left + var bsize = Math.ceil(original.height / blocks); + for (var i = 0; i < trim_info.left; i = i + bsize) { + for (var j = trim_info.top; j < trim_info.top + original.height; j = j + bsize) { + ctx.drawImage(original, + 0, j - trim_info.top, bsize, bsize, + i, j, bsize, bsize); + } + } + + //right + var bsize = Math.ceil(original.height / blocks); + for (var i = trim_info.left + original.width; i < canvas.width; i = i + bsize) { + for (var j = trim_info.top; j < trim_info.top + original.height; j = j + bsize) { + ctx.drawImage(original, + original.width - bsize, j - trim_info.top, bsize, bsize, + i, j, bsize, bsize); + } + } + + //corners + var bsize = Math.ceil(Math.min(original.width, original.height) / blocks); + + //top left + for (var i = 0; i < trim_info.left; i = i + bsize) { + for (var j = 0; j < trim_info.top; j = j + bsize) { + ctx.drawImage(original, + 0, 0, bsize, bsize, + i, j, bsize, bsize); + } + } + + //top right + for (var i = trim_info.left + original.width; i < canvas.width; i = i + bsize) { + for (var j = 0; j < trim_info.top; j = j + bsize) { + ctx.drawImage(original, + original.width - bsize, 0, bsize, bsize, + i, j, bsize, bsize); + } + } + + //bottom left + for (var i = 0; i < trim_info.left; i = i + bsize) { + for (var j = trim_info.top + original.height; j < canvas.height; j = j + bsize) { + ctx.drawImage(original, + 0, original.height - bsize, bsize, bsize, + i, j, bsize, bsize); + } + } + + //bottom right + for (var i = trim_info.left + original.width; i < canvas.width; i = i + bsize) { + for (var j = trim_info.top + original.height; j < canvas.height; j = j + bsize) { + ctx.drawImage(original, + original.width - bsize, original.height - bsize, bsize, bsize, + i, j, bsize, bsize); + } + } + + + //add blur + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var blurred = ImageFilters.BoxBlur(img, params.blur_h, params.blur_v, params.blur_power); + ctx.putImageData(blurred, 0, 0); + } + +} + +export default Tools_contentFill_class; \ No newline at end of file diff --git a/frontend/src/js/modules/tools/keypoints.js b/frontend/src/js/modules/tools/keypoints.js new file mode 100644 index 0000000..bab743d --- /dev/null +++ b/frontend/src/js/modules/tools/keypoints.js @@ -0,0 +1,225 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Helper_class from './../../libs/helpers.js'; +import ImageFilters_class from './../../libs/imagefilters.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +/** + * SIFT: scale-invariant-feature-transform, keypoints + * + * @author ViliusL + */ +class Tools_keypoints_class { + + constructor() { + this.Helper = new Helper_class(); + this.Base_layers = new Base_layers_class(); + this.ImageFilters = ImageFilters_class; + + //contrast check, smaller - more points, better accuracy, but slower + this.avg_offset = 50; + + /** + * how much pixels to check for each side to get average + */ + this.avg_step = 4; + } + + //generate key points for image + keypoints(return_data) { + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var W = config.layer.width; + var H = config.layer.height; + + //get canvas from layer + var clone = this.Base_layers.convert_layer_to_canvas(); + var ctx = clone.getContext("2d"); + + //greyscale + var imageData = ctx.getImageData(0, 0, W, H); + var data = this.convert_to_grayscale(imageData); + ctx.putImageData(data, 0, 0); + + //make few copies and blur each + var n = 5; + var copies = []; + for (var i = 0; i < n; i++) { + var tmp_canvas = document.createElement('canvas'); + tmp_canvas.width = W; + tmp_canvas.height = H; + var ctx_i = tmp_canvas.getContext("2d"); + ctx_i.drawImage(clone, 0, 0); + + //Gausian blur + var imageData = ctx_i.getImageData(0, 0, W, H); + var filtered = this.ImageFilters.GaussianBlur(imageData, i + 0.5); //add effect + ctx_i.putImageData(filtered, 0, 0); + + copies.push(tmp_canvas); + } + + //find extreme points + var points = []; + var n0 = this.avg_step * 2 + 1; + for (var c = 1; c < copies.length - 1; c++) { + var imageData = copies[c].getContext("2d").getImageData(0, 0, W, H).data; + var imageData0 = copies[c - 1].getContext("2d").getImageData(0, 0, W, H).data; + var imageData2 = copies[c + 1].getContext("2d").getImageData(0, 0, W, H).data; + for (var j = this.avg_step; j < H - this.avg_step; j++) { + for (var i = this.avg_step; i < W - this.avg_step; i++) { + var x = (i + j * W) * 4; + if (imageData[x + 3] == 0) + continue; //transparent + if (imageData[x] < imageData[x - 4] || imageData[x] < imageData[x + 4] || imageData[x] > imageData[x - 4] || imageData[x] > imageData[x + 4]) { + var x_pre = (i + (j - 1) * W) * 4; + var x_post = (i + (j + 1) * W) * 4; + //calc average + var area_average = 0; + for (var l = -this.avg_step; l <= this.avg_step; l++) { + var avgi = (i + (j - l) * W) * 4; + for (var a = -this.avg_step; a <= this.avg_step; a++) { + area_average += imageData[avgi + 4 * a]; + } + } + area_average = area_average / (n0 * n0); + //max + if (imageData[x] + this.avg_offset < area_average) { + var min = Math.min(imageData[x_pre - 4], imageData[x_pre], imageData[x_pre + 4], imageData[x - 4], imageData[x + 4], imageData[x_post - 4], imageData[x_post], imageData[x_post + 4]); + if (imageData[x] <= min) { + var min0 = Math.min(imageData0[x_pre - 4], imageData0[x_pre], imageData0[x_pre + 4], imageData0[x - 4], imageData0[x + 4], imageData0[x_post - 4], imageData0[x_post], imageData0[x_post + 4]); + if (imageData[x] <= min0) { + var min2 = Math.min(imageData2[x_pre - 4], imageData2[x_pre], imageData2[x_pre + 4], imageData2[x - 4], imageData2[x + 4], imageData2[x_post - 4], imageData2[x_post], imageData2[x_post + 4]); + if (imageData[x] <= min2) + points.push({ + x: i, + y: j, + w: Math.round(area_average - imageData[x] - this.avg_offset) + }); + } + } + continue; + } + //min + if (imageData[x] - this.avg_offset > area_average) { + var max = Math.max(imageData[x_pre - 4], imageData[x_pre], imageData[x_pre + 4], imageData[x - 4], imageData[x + 4], imageData[x_post - 4], imageData[x_post], imageData[x_post + 4]); + if (imageData[x] >= max) { + var max0 = Math.max(imageData0[x_pre - 4], imageData0[x_pre], imageData0[x_pre + 4], imageData0[x - 4], imageData0[x + 4], imageData0[x_post - 4], imageData0[x_post], imageData0[x_post + 4]); + if (imageData[x] >= max0) { + var max2 = Math.max(imageData2[x_pre - 4], imageData2[x_pre], imageData2[x_pre + 4], imageData2[x - 4], imageData2[x + 4], imageData2[x_post - 4], imageData2[x_post], imageData2[x_post + 4]); + if (imageData[x] >= max2) { + points.push({ + x: i, + y: j, + w: Math.round(imageData[x] - area_average - this.avg_offset) + }); + } + } + } + } + } + } + } + } + //make unique + for (var i = 0; i < points.length; i++) { + for (var j = 0; j < points.length; j++) { + if (i != j && points[i].x == points[j].x && points[i].y == points[j].y) { + points.splice(i, 1); + i--; + break; + } + } + } + + //show points? + if (return_data === undefined || return_data !== true) { + alertify.success('key points: ' + points.length); + + var size = 3; + ctx.clearRect(0, 0, clone.width, clone.height); + ctx.fillStyle = "#ff0000"; + for (var i in points) { + var point = points[i]; + ctx.beginPath(); + ctx.rect(point.x - Math.floor(size / 2) + 1, point.y - Math.floor(size / 2) + 1, size, size); + ctx.fill(); + } + + //show + var params = []; + params.type = 'image'; + params.name = config.layer.name + ' + key points'; + params.data = clone.toDataURL("image/png"); + params.x = parseInt(clone.dataset.x); + params.y = parseInt(clone.dataset.y); + params.width = clone.width; + params.height = clone.height; + app.State.do_action( + new app.Actions.Bundle_action('keypoints', 'Key-Points', [ + new app.Actions.Insert_layer_action(params) + ]) + ); + + clone.width = 1; + clone.height = 1; + } + else { + //sort by weights + points.sort(function (a, b) { + return parseFloat(b.w) - parseFloat(a.w); + }); + + clone.width = 1; + clone.height = 1; + + return { + points: points, + }; + } + } + + //returns average value of requested area from greyscale image + //area = {x, y, w, h} + get_area_average(area, imageData, i, j, size) { + var imgData = imageData.data; + var sum = 0; + var n = 0; + size = size / 100; //prepare to use 1-100% values + var stop_x = i + Math.round(size * area.x) + Math.round(size * area.w); + var stop_y = j + Math.round(size * area.y) + Math.round(size * area.h); + var img_width4 = imageData.width * 4; + var k0, k; + for (var y = j + Math.round(size * area.y); y < stop_y; y++) { + k0 = y * img_width4; + for (var x = i + Math.round(size * area.x); x < stop_x; x++) { + k = k0 + (x * 4); + sum = sum + imgData[k]; + n++; + } + } + return Math.round(sum / n); + } + + convert_to_grayscale(data) { + var imgData = data.data; + var grey; + + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + grey = Math.round(0.2126 * imgData[i] + 0.7152 * imgData[i + 1] + 0.0722 * imgData[i + 2]); + imgData[i] = grey; + imgData[i + 1] = grey; + imgData[i + 2] = grey; + } + return data; + } +} + +export default Tools_keypoints_class; \ No newline at end of file diff --git a/frontend/src/js/modules/tools/replace_color.js b/frontend/src/js/modules/tools/replace_color.js new file mode 100644 index 0000000..4aa40d9 --- /dev/null +++ b/frontend/src/js/modules/tools/replace_color.js @@ -0,0 +1,127 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Dialog_class from './../../libs/popup.js'; +import Helper_class from './../../libs/helpers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Tools_replaceColor_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + } + + replace_color() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Replace color', + preview: true, + on_change: function (params, canvas_preview, w, h) { + var img = canvas_preview.getImageData(0, 0, w, h); + var data = _this.do_replace(img, params); + canvas_preview.putImageData(data, 0, 0); + }, + params: [ + {name: "target", title: "Target:", value: config.COLOR, type: 'color'}, + {name: "replacement", title: "Replacement:", value: '#ff0000', type: 'color'}, + {name: "power", title: "Power:", value: "20", range: [0, 255]}, + {name: "alpha", title: "Alpha:", value: "255", range: [0, 255]}, + {name: "mode", title: "Mode:", values: ['Advanced', 'Simple']}, + ], + on_finish: function (params) { + _this.save_alpha(params); + }, + }; + this.POP.show(settings); + } + + save_alpha(params) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.do_replace(img, params); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + do_replace(data, params) { + var target = params.target; + var replacement = params.replacement; + var power = params.power; + var alpha = params.alpha; + var mode = params.mode; + + var imgData = data.data; + var target_rgb = this.Helper.hexToRgb(target); + var target_hsl = this.Helper.rgbToHsl(target_rgb.r, target_rgb.g, target_rgb.b); + var target_normalized = this.Helper.hslToRgb(target_hsl.h, target_hsl.s, 0.5); + + var replacement_rgb = this.Helper.hexToRgb(replacement); + var replacement_hsl = this.Helper.rgbToHsl(replacement_rgb.r, replacement_rgb.g, replacement_rgb.b); + + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + + if (mode == 'Simple') { + //simple replace + + //calculate difference from requested color, and change alpha + var diff = (Math.abs(imgData[i] - target_rgb.r) + + Math.abs(imgData[i + 1] - target_rgb.g) + + Math.abs(imgData[i + 2] - target_rgb.b)) / 3; + if (diff > power) + continue; + + imgData[i] = replacement_rgb.r; + imgData[i + 1] = replacement_rgb.g; + imgData[i + 2] = replacement_rgb.b; + if (alpha < 255) + imgData[i + 3] = alpha; + } + else { + //advanced replace using HSL + + var hsl = this.Helper.rgbToHsl(imgData[i], imgData[i + 1], imgData[i + 2]); + var normalized = this.Helper.hslToRgb(hsl.h, hsl.s, 0.5); + var diff = (Math.abs(normalized.r - target_normalized.r) + + Math.abs(normalized.g - target_normalized.g) + + Math.abs(normalized.b - target_normalized.b)) / 3; + if (diff > power) + continue; + + //change to new color with existing luminance + var normalized_final = this.Helper.hslToRgb( + replacement_hsl.h, + replacement_hsl.s, + hsl.l * (replacement_hsl.l) + ); + + imgData[i] = normalized_final.r; + imgData[i + 1] = normalized_final.g; + imgData[i + 2] = normalized_final.b; + if (alpha < 255) + imgData[i + 3] = alpha; + } + } + return data; + } + +} + +export default Tools_replaceColor_class; \ No newline at end of file diff --git a/frontend/src/js/modules/tools/restore_alpha.js b/frontend/src/js/modules/tools/restore_alpha.js new file mode 100644 index 0000000..a7554dc --- /dev/null +++ b/frontend/src/js/modules/tools/restore_alpha.js @@ -0,0 +1,72 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Dialog_class from './../../libs/popup.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Tools_restoreAlpha_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + } + + restore_alpha() { + var _this = this; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + var settings = { + title: 'Restore Alpha', + preview: true, + on_change: function (params, canvas_preview, w, h) { + var img = canvas_preview.getImageData(0, 0, w, h); + var data = _this.recover_alpha(img, params.level); + canvas_preview.putImageData(data, 0, 0); + }, + params: [ + {name: "level", title: "Level:", value: "128", range: [0, 255]}, + ], + on_finish: function (params) { + _this.save_alpha(params.level); + }, + }; + this.POP.show(settings); + } + + save_alpha(level) { + //get canvas from layer + var canvas = this.Base_layers.convert_layer_to_canvas(null, true); + var ctx = canvas.getContext("2d"); + + //change data + var img = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = this.recover_alpha(img, level); + ctx.putImageData(data, 0, 0); + + //save + return app.State.do_action( + new app.Actions.Update_layer_image_action(canvas) + ); + } + + recover_alpha(data, level) { + var imgData = data.data; + var tmp; + level = parseInt(level); + for (var i = 0; i < imgData.length; i += 4) { + tmp = imgData[i + 3] + level; + if (tmp > 255) { + tmp = 255; + } + imgData[i + 3] = tmp; + } + return data; + } + +} + +export default Tools_restoreAlpha_class; \ No newline at end of file diff --git a/frontend/src/js/modules/tools/search.js b/frontend/src/js/modules/tools/search.js new file mode 100644 index 0000000..fb9f622 --- /dev/null +++ b/frontend/src/js/modules/tools/search.js @@ -0,0 +1,15 @@ +import Base_search_class from './../../core/base-search.js'; + +class Tools_search_class { + + constructor() { + this.Base_search = new Base_search_class(); + } + + search() { + this.Base_search.search(); + } + +} + +export default Tools_search_class; \ No newline at end of file diff --git a/frontend/src/js/modules/tools/settings.js b/frontend/src/js/modules/tools/settings.js new file mode 100644 index 0000000..202ef20 --- /dev/null +++ b/frontend/src/js/modules/tools/settings.js @@ -0,0 +1,171 @@ +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Helper_class from './../../libs/helpers.js'; +import Base_gui_class from './../../core/base-gui.js'; + +class Tools_settings_class { + + constructor() { + this.Base_gui = new Base_gui_class(); + this.POP = new Dialog_class(); + this.Helper = new Helper_class(); + + this.default_units_config = { + pixels: 'px', + inches: '"', + centimeters: 'cm', + millimetres: 'mm', + }; + } + + settings() { + var _this = this; + var transparency_values = ['squares', 'green', 'grey']; + var resolutions_values = [72, 150, 300, 600]; + var default_units_all = Object.keys(this.default_units_config); + var transparency = this.get_setting('transparency'); + var theme = this.get_setting('theme'); + var snap = this.get_setting('snap'); + var guides = this.get_setting('guides'); + var safe_search = this.get_setting('safe_search'); + var exit_confirm = this.get_setting('exit_confirm'); + var default_units = this.get_setting('default_units'); + var resolution = this.get_setting('resolution'); + var thick_guides = this.get_setting('thick_guides'); + var enable_autoresize = this.get_setting('enable_autoresize'); + + var settings = { + title: 'Settings', + params: [ + {name: "transparency", title: "Transparent:", value: transparency}, + {name: "transparency_type", title: "Transparency background:", type: "select", + value: config.TRANSPARENCY_TYPE, values: transparency_values}, + {name: "theme", title: "Theme", values: config.themes, value: theme, type: "select"}, + {name: "default_units", title: "Units", values: default_units_all, value: default_units, type: "select"}, + {name: "resolution", title: "Resolution:", type: "select", + value: resolution, values: resolutions_values}, + {name: "snap", title: "Enable snap:", value: snap}, + {name: "guides", title: "Enable guides:", value: guides}, + {name: "safe_search", title: "Safe search:", value: safe_search}, + {name: "exit_confirm", title: "Exit confirmation:", value: exit_confirm}, + {name: "thick_guides", title: "Thick guides:", value: thick_guides}, + {name: "enable_autoresize", title: "Enable autoresize:", value: enable_autoresize}, + ], + on_change: function (params) { + this.Base_gui.change_theme(params.theme); + }, + on_cancel: function (params) { + this.Base_gui.change_theme(theme); + }, + on_finish: function (params) { + _this.save_values(params); + }, + }; + this.POP.show(settings); + } + + save_values(params) { + + //save + this.save_setting('theme', params.theme); + this.save_setting('transparency', params.transparency); + this.save_setting('transparency_type', params.transparency_type); + this.save_setting('snap', params.snap); + this.save_setting('guides', params.guides); + this.save_setting('safe_search', params.safe_search); + this.save_setting('exit_confirm', params.exit_confirm); + this.save_setting('default_units', params.default_units); + this.save_setting('default_units_short', this.default_units_config[params.default_units]); + this.save_setting('resolution', params.resolution); + this.save_setting('thick_guides', params.thick_guides); + this.save_setting('enable_autoresize', params.enable_autoresize); + + //update config + config.TRANSPARENCY = this.get_setting('transparency'); + config.TRANSPARENCY_TYPE = this.get_setting('transparency_type'); + config.SNAP = this.get_setting('snap'); + config.guides_enabled = this.get_setting('guides'); + this.Base_gui.change_theme(this.get_setting('theme')); + this.Base_gui.GUI_information.update_units(); + + //finish + this.Base_gui.prepare_canvas(); + config.need_render = true; + } + + /** + * set global setting. Values can be string(1 or 0 will be converted to boolean) or boolean + * + * @param key + * @param value + */ + save_setting(key, value) { + //prepare + if(value === true){ + value = 1; + } + if(value === false){ + value = 0; + } + + this.Helper.setCookie(key, value); + } + + /** + * get global setting. If settings does not exists, default valye will be used. + * + * @param key + * @returns {Object|string} + */ + get_setting(key) { + var default_values = { + 'theme': null, + 'transparency': false, + 'snap': true, + 'guides': true, + 'safe_search': true, + 'exit_confirm': true, + 'default_units': Object.keys(this.default_units_config)[0], + 'default_units_short': Object.values(this.default_units_config)[0], + 'resolution': 72, + 'thick_guides': false, + 'enable_autoresize': config.enable_autoresize_by_default, + }; + + var value = this.Helper.getCookie(key); + if(value == null && default_values[key] != undefined){ + //set default value + value = default_values[key]; + } + if(key == 'safe_search' && config.safe_search_can_be_disabled === false){ + //not allowed + value = 1; + } + if(key == 'theme' && value == null) { + value = config.themes[0]; + /*if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches + && config.themes.includes('dark')) { + //dark mode + value = 'dark'; + } + else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches + && config.themes.includes('light')) { + //light mode + value = 'light'; + }*/ + } + + //finalize values + if(value === 1){ + value = true; + } + if(value === 0){ + value = false; + } + + return value; + } + +} + +export default Tools_settings_class; \ No newline at end of file diff --git a/frontend/src/js/modules/tools/sprites.js b/frontend/src/js/modules/tools/sprites.js new file mode 100644 index 0000000..d8b563b --- /dev/null +++ b/frontend/src/js/modules/tools/sprites.js @@ -0,0 +1,123 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Dialog_class from './../../libs/popup.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; +import Image_trim_class from './../image/trim.js'; +import Base_gui_class from './../../core/base-gui.js'; + +class Tools_sprites_class { + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Image_trim = new Image_trim_class(); + this.Base_gui = new Base_gui_class(); + } + + sprites() { + var _this = this; + + var settings = { + title: 'Sprites', + params: [ + {name: "gap", title: "Gap:", value: "50", values: ["0", "10", "50", "100"]}, + ], + on_finish: function (params) { + _this.generate_sprites(params.gap); + }, + }; + this.POP.show(settings); + } + + generate_sprites(gap, sprite_width) { + gap = parseInt(gap); + + if (config.layers.length == 1) { + alertify.error('There is only 1 layer.'); + return false; + } + + var xx = 0; + var yy = 0; + var max_height = 0; + let actions = []; + let new_height = config.HEIGHT; + let new_width = config.WIDTH; + + //collect trim info + var trim_details_array = []; + for (var i = 0; i < config.layers.length; i++) { + var layer = config.layers[i]; + if (layer.visible == false) + continue; + + trim_details_array[layer.id] = this.Image_trim.get_trim_info(layer.id); + } + + //move layers + for (var i = 0; i < config.layers.length; i++) { + var layer = config.layers[i]; + if (layer.visible == false) + continue; + + var trim_details = trim_details_array[layer.id]; + if (new_width == trim_details.left) { + //empty layer + continue; + } + var width = new_width - trim_details.left - trim_details.right; + var height = config.HEIGHT - trim_details.top - trim_details.bottom; + + if (xx + width > new_width) { + xx = 0; + yy += max_height; + max_height = 0; + } + if (yy % gap > 0 && gap > 0) { + yy = yy - yy % gap + gap; + } + if (yy + height > new_height) { + new_height = parseInt(yy + height); + this.Base_gui.prepare_canvas(); + } + + actions.push( + new app.Actions.Update_layer_action(layer.id, { + x: layer.x + xx - trim_details.left, + y: layer.y + yy - trim_details.top + }) + ); + + xx += width; + if (gap > 0) { + xx = xx - xx % gap + gap; + } + + if (height > max_height) { + max_height = height; + } + if (xx > new_width) { + xx = 0; + yy += max_height; + max_height = 0; + } + } + actions.push( + new app.Actions.Prepare_canvas_action('undo'), + new app.Actions.Update_config_action({ + WIDTH: new_width, + HEIGHT: new_height + }), + new app.Actions.Prepare_canvas_action('do') + ); + + app.State.do_action( + new app.Actions.Bundle_action('sprites', 'Sprites', actions) + ); + + } + +} + +export default Tools_sprites_class; \ No newline at end of file diff --git a/frontend/src/js/modules/tools/translate.js b/frontend/src/js/modules/tools/translate.js new file mode 100644 index 0000000..e02190c --- /dev/null +++ b/frontend/src/js/modules/tools/translate.js @@ -0,0 +1,70 @@ +import config from './../../config.js'; +import Helper_class from './../../libs/helpers.js'; +import Translate_class from './../../libs/jquery.translate.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; + +var instance = null; + +class Tools_translate_class { + + constructor() { + //singleton + if (instance) { + return instance; + } + instance = this; + + this.Helper = new Helper_class(); + this.translations = {}; + this.trans_lang_codes = []; + + this.load_translations(); + } + + //change language + translate(lang_code, element) { + if (lang_code == undefined) { + lang_code = this.Helper.getCookie('language'); + if (!lang_code) { + return; + } + } + + if (lang_code != undefined && lang_code != config.LANG) { + //save cookie + this.Helper.setCookie('language', lang_code); + } + + if (this.trans_lang_codes.includes(lang_code) || lang_code == 'en') { + //translate + $(element || 'body').translate({lang: lang_code, t: this.translations}); + config.LANG = lang_code; + } + else { + alertify.error('Translate error, can not find dictionary: ' + lang_code); + } + } + + load_translations() { + var _this = this; + var modules_context = require.context("./../../languages/", true, /\.json$/); + modules_context.keys().forEach(function (key) { + if (key.indexOf('Base' + '/') < 0 && key.indexOf('empty') < 0) { + var moduleKey = key.replace('./', '').replace('.json', ''); + var classObj = modules_context(key); + + for(var i in classObj){ + if(_this.translations[i] == undefined){ + _this.translations[i] = { + en: i, + }; + } + _this.translations[i][moduleKey] = classObj[i]; + } + _this.trans_lang_codes.push(moduleKey); + } + }); + } +} + +export default Tools_translate_class; diff --git a/frontend/src/js/modules/view/full_screen.js b/frontend/src/js/modules/view/full_screen.js new file mode 100644 index 0000000..d5c6ca7 --- /dev/null +++ b/frontend/src/js/modules/view/full_screen.js @@ -0,0 +1,20 @@ +class View_fullScreen_class { + + constructor() {} + + /** + * toggle full-screen + */ + fs() { + if (!document.fullscreenElement) { + document.documentElement.requestFullscreen(); + } + else { + if (document.exitFullscreen) { + document.exitFullscreen(); + } + } + } +} + +export default View_fullScreen_class; diff --git a/frontend/src/js/modules/view/grid.js b/frontend/src/js/modules/view/grid.js new file mode 100644 index 0000000..49d85ef --- /dev/null +++ b/frontend/src/js/modules/view/grid.js @@ -0,0 +1,48 @@ +import config from './../../config.js'; +import Helper_class from './../../libs/helpers.js'; +import Base_gui_class from './../../core/base-gui.js'; + +var instance = null; + +class View_grid_class { + + constructor() { + //singleton + if (instance) { + return instance; + } + instance = this; + + this.GUI = new Base_gui_class(); + this.Helper = new Helper_class(); + + this.set_events(); + } + + set_events() { + document.addEventListener('keydown', (event) => { + var code = event.keyCode; + if (this.Helper.is_input(event.target)) + return; + + if (code == 71 && event.ctrlKey != true && event.metaKey != true) { + //G - grid + this.grid({visible: !this.GUI.grid}); + event.preventDefault(); + } + }, false); + } + + grid() { + if (this.GUI.grid == false) { + this.GUI.grid = true; + } + else { + this.GUI.grid = false; + } + config.need_render = true; + } + +} + +export default View_grid_class; \ No newline at end of file diff --git a/frontend/src/js/modules/view/guides.js b/frontend/src/js/modules/view/guides.js new file mode 100644 index 0000000..945ac18 --- /dev/null +++ b/frontend/src/js/modules/view/guides.js @@ -0,0 +1,146 @@ +import config from './../../config.js'; +import Dialog_class from './../../libs/popup.js'; +import Helper_class from './../../libs/helpers.js'; +import Base_layers_class from './../../core/base-layers.js'; +import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; +import Tools_settings_class from './../tools/settings.js'; +import app from './../../app.js'; + +class View_guides_class { + + + constructor() { + this.POP = new Dialog_class(); + this.Base_layers = new Base_layers_class(); + this.Tools_settings = new Tools_settings_class(); + this.Helper = new Helper_class(); + } + + insert() { + var _this = this; + var units = this.Tools_settings.get_setting('default_units'); + var resolution = this.Tools_settings.get_setting('resolution'); + + //convert units + var position = 20; + var position = this.Helper.get_user_unit(position, units, resolution); + + var settings = { + title: 'Insert guides', + params: [ + {name: "type", title: "Type:", values: ["Vertical", "Horizontal"], value :"Vertical"}, + {name: "position", title: "Position:", value: position}, + ], + on_finish: function (params) { + _this.insert_handler(params); + }, + }; + this.POP.show(settings); + } + + insert_handler(data){ + var type = data.type; + var position = parseFloat(data.position); + var units = this.Tools_settings.get_setting('default_units'); + var resolution = this.Tools_settings.get_setting('resolution'); + + //convert units + position = this.Helper.get_internal_unit(position, units, resolution); + + var x = null; + var y = null; + if(type == 'Vertical') + x = position; + if(type == 'Horizontal') + y = position; + + //update + config.guides.push({x: x, y: y}); + + if(config.guides_enabled == false){ + //was disabled + config.guides_enabled = true; + this.Helper.setCookie('guides', 1); + alertify.warning('Guides enabled.'); + } + + config.need_render = true; + } + + update(){ + var _this = this; + var units = this.Tools_settings.get_setting('default_units'); + var resolution = this.Tools_settings.get_setting('resolution'); + + var params = []; + for(var i in config.guides){ + var guide = config.guides[i]; + + //convert units + var value = guide.x; + var value = this.Helper.get_user_unit(value, units, resolution); + + if(guide.y === null) { + params.push({name: i, title: "Vertical:", value: value}); + } + } + for(var i in config.guides){ + var guide = config.guides[i]; + + //convert units + var value = guide.y; + var value = this.Helper.get_user_unit(value, units, resolution); + + if(guide.x === null) { + params.push({name: i, title: "Horizontal:", value: value}); + } + } + + var settings = { + title: 'Update guides', + params: params, + on_finish: function (params) { + _this.update_handler(params); + }, + }; + this.POP.show(settings); + } + + update_handler(data){ + var units = this.Tools_settings.get_setting('default_units'); + var resolution = this.Tools_settings.get_setting('resolution'); + + //update + for (var i in data) { + var key = parseInt(i); + var value = parseFloat(data[i]); + + //convert units + value = this.Helper.get_internal_unit(value, units, resolution); + + if (config.guides[key].x === null) + config.guides[key].y = value; + else + config.guides[key].x = value; + } + + //remove empty + for (var i = 0; i < config.guides.length; i++) { + if(config.guides[i].x === 0 || config.guides[i].y === 0 + || isNaN(config.guides[i].x) || isNaN( config.guides[i].y)){ + config.guides.splice(i, 1); + i--; + } + } + + config.need_render = true; + } + + remove(params) { + config.guides = []; + config.need_render = true; + } + +} + +export default View_guides_class; \ No newline at end of file diff --git a/frontend/src/js/modules/view/ruler.js b/frontend/src/js/modules/view/ruler.js new file mode 100644 index 0000000..096adc0 --- /dev/null +++ b/frontend/src/js/modules/view/ruler.js @@ -0,0 +1,205 @@ +import config from './../../config.js'; +import Helper_class from './../../libs/helpers.js'; +import Base_gui_class from './../../core/base-gui.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Tools_settings_class from './../tools/settings.js'; + +var instance = null; + +class View_ruler_class { + + constructor() { + //singleton + if (instance) { + return instance; + } + instance = this; + + this.GUI = new Base_gui_class(); + this.Base_layers = new Base_layers_class(); + this.Tools_settings = new Tools_settings_class(); + this.Helper = new Helper_class(); + + this.set_events(); + } + + set_events() { + var _this = this; + + window.addEventListener('resize', function (event) { + //resize + _this.prepare_ruler(); + _this.render_ruler(); + }, false); + document.addEventListener('keydown', (event) => { + var code = event.code; + if (this.Helper.is_input(event.target)) + return; + + if (event.code == "KeyU" && event.ctrlKey != true && event.metaKey != true) { + _this.ruler(); + event.preventDefault(); + } + }, false); + } + + ruler() { + var ruler_left = document.getElementById('ruler_left'); + var ruler_top = document.getElementById('ruler_top'); + var middle_area = document.getElementById('middle_area'); + + if(config.ruler_active == false){ + //activate + config.ruler_active = true; + document.getElementById('middle_area').classList.add('has-ruler'); + ruler_left.style.display = 'block'; + ruler_top.style.display = 'block'; + + this.prepare_ruler(); + this.render_ruler(); + } + else{ + //deactivate + config.ruler_active = false; + document.getElementById('middle_area').classList.remove('has-ruler'); + ruler_left.style.display = 'none'; + ruler_top.style.display = 'none'; + } + + this.GUI.prepare_canvas(); + + config.need_render = true; + } + + prepare_ruler(){ + if(config.ruler_active == false) + return; + + var ruler_left = document.getElementById('ruler_left'); + var ruler_top = document.getElementById('ruler_top'); + var middle_area = document.getElementById('middle_area'); + + var middle_area_width = middle_area.clientWidth; + var middle_area_height = middle_area.clientHeight; + + ruler_left.width = 15; + ruler_left.height = middle_area_height - 20; + + ruler_top.width = middle_area_width - 20; + ruler_top.height = 15; + } + + render_ruler(){ + if(config.ruler_active == false) + return; + + var units = this.Tools_settings.get_setting('default_units'); + var resolution = this.Tools_settings.get_setting('resolution'); + + var ruler_left = document.getElementById('ruler_left'); + var ruler_top = document.getElementById('ruler_top'); + + var ctx_left = ruler_left.getContext("2d"); + var ctx_top = ruler_top.getContext("2d"); + + var color = '#111'; + var size = 15; + + //calc step + var step = Math.ceil(10 * config.ZOOM); + while (step < 5) { + step = step * 2; + } + while (step > 10) { + step = Math.ceil(step / 2); + } + var step_big = step * 10; + + //calc begin/end point + var begin_x = Math.max(0, ruler_top.width / 2 - config.WIDTH * config.ZOOM / 2); + var begin_y = Math.max(0, ruler_left.height / 2 - config.HEIGHT * config.ZOOM / 2); + + var end_x = Math.min(ruler_top.width, ruler_top.width / 2 + config.WIDTH * config.ZOOM / 2); + var end_y = Math.min(ruler_left.height, ruler_left.height / 2 + config.HEIGHT * config.ZOOM / 2); + + //left + ctx_left.strokeStyle = color; + ctx_left.lineWidth = 1; + ctx_left.font = "11px Arial"; + + ctx_left.clearRect(0, 0, ruler_left.width, ruler_left.height); + + ctx_left.beginPath(); + for (var i = begin_y; i < end_y; i += step) { + ctx_left.moveTo(10, i + 0.5); + ctx_left.lineTo(size, i + 0.5); + } + ctx_left.stroke(); + + ctx_left.beginPath(); + for (var i = begin_y; i <= end_y; i += step_big) { + ctx_left.moveTo(0, i + 0.5); + ctx_left.lineTo(size, i + 0.5); + + var global_pos = this.Base_layers.get_world_coords(0, i - begin_y); + var value = this.Helper.get_user_unit(global_pos.y, units, resolution); + + if(units == 'inches'){ + //more decimals value + var text = this.Helper.number_format(value, 1); + } + else{ + var text = Math.ceil(value); + } + text = text.toString(); + + //text + for (var j = 0; j < text.length; j++) { + var letter = text.charAt(j); + var line_height = 10; + ctx_left.fillText(letter, 1, i + 11 + j * line_height); + } + } + ctx_left.stroke(); + + //top + ctx_top.strokeStyle = color; + ctx_top.lineWidth = 1; + ctx_top.font = "11px Arial"; + + ctx_top.clearRect(0, 0, ruler_top.width, ruler_top.height); + + ctx_top.beginPath(); + for (var i = begin_x; i < end_x; i += step) { + var y = (i / step_big == parseInt(i / step_big)) ? 0 : step; + ctx_top.moveTo(i + 0.5, 10); + ctx_top.lineTo(i + 0.5, size); + } + ctx_top.stroke(); + + ctx_top.beginPath(); + for (var i = begin_x; i <= end_x; i += step_big) { + ctx_top.moveTo(i + 0.5, 0); + ctx_top.lineTo(i + 0.5, size); + + var global_pos = this.Base_layers.get_world_coords(i - begin_x, 0); + var value = this.Helper.get_user_unit(global_pos.x, units, resolution); + + if(units == 'inches'){ + //more decimals value + var text = this.Helper.number_format(value, 1); + } + else{ + var text = Math.ceil(value); + } + text = text.toString(); + + //text + ctx_top.fillText(text, i + 3, 9); + } + ctx_top.stroke(); + } + +} + +export default View_ruler_class; diff --git a/frontend/src/js/modules/view/zoom.js b/frontend/src/js/modules/view/zoom.js new file mode 100644 index 0000000..79eeb48 --- /dev/null +++ b/frontend/src/js/modules/view/zoom.js @@ -0,0 +1,26 @@ +import GUI_preview_class from './../../core/gui/gui-preview.js'; + +class View_zoom_class { + + constructor() { + this.GUI_preview = new GUI_preview_class(); + } + + in() { + this.GUI_preview.zoom(1); + } + + out() { + this.GUI_preview.zoom(-1); + } + + original() { + this.GUI_preview.zoom(100); + } + + auto() { + this.GUI_preview.zoom_auto(); + } +} + +export default View_zoom_class; diff --git a/frontend/src/js/tools/animation.js b/frontend/src/js/tools/animation.js new file mode 100644 index 0000000..de7d588 --- /dev/null +++ b/frontend/src/js/tools/animation.js @@ -0,0 +1,116 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import GUI_tools_class from './../core/gui/gui-tools.js'; +import Base_gui_class from './../core/base-gui.js'; +import Base_selection_class from './../core/base-selection.js'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Animation_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.GUI_tools = new GUI_tools_class(); + this.Base_gui = new Base_gui_class(); + this.name = 'animation'; + this.intervalID = null; + this.index = 0; + this.toggle_layer_visibility_action = new app.Actions.Toggle_layer_visibility_action(); + + this.disable_selection(ctx); + } + + load() { + //nothing + } + + render(ctx, layer) { + //nothing + } + + /** + * disable_selection + */ + disable_selection(ctx) { + var sel_config = { + enable_background: false, + enable_borders: false, + enable_controls: false, + enable_rotation: false, + enable_move: false, + data_function: function () { + return null; + }, + }; + this.Base_selection = new Base_selection_class(ctx, sel_config, this.name); + } + + on_params_update(data) { + if(data.key != "play") + return; + + var params = this.getParams(); + if (config.layers.length == 1) { + alertify.error('Can not animate 1 layer.'); + return; + } + this.stop(); + + if (params.play == true) { + this.start(params.delay); + } + } + + on_activate() { + return [ + new app.Actions.Stop_animation_action(false) + ]; + } + + on_leave() { + return [ + new app.Actions.Stop_animation_action(true) + ]; + } + + start(delay) { + var _this = this; + delay = parseInt(delay); + if (delay < 0) + delay = 50; + + this.intervalID = window.setInterval(function () { + _this.play(_this); + }, delay); + } + + stop() { + new app.Actions.Stop_animation_action(true).do(); + } + + play(_this) { + + for (var i in config.layers) { + config.layers[i].visible = false; + } + + //show 1 + if (config.layers[this.index] != undefined) { + this.toggle_layer_visibility_action.layer_id = config.layers[this.index].id; + this.toggle_layer_visibility_action.do(); + } + + //change index + if (config.layers[this.index + 1] != undefined) { + this.index++; + } + else { + this.index = 0; + } + } + +} +; +export default Animation_class; diff --git a/frontend/src/js/tools/blur.js b/frontend/src/js/tools/blur.js new file mode 100644 index 0000000..ab17bfb --- /dev/null +++ b/frontend/src/js/tools/blur.js @@ -0,0 +1,140 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; +import ImageFilters from './../libs/imagefilters.js'; +import Helper_class from './../libs/helpers.js'; + +class Blur_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.ctx = ctx; + this.name = 'blur'; + this.tmpCanvas = null; + this.tmpCanvasCtx = null; + this.started = false; + } + + load() { + this.default_events(); + } + + default_dragMove(event) { + if (config.TOOL.name != this.name) + return; + this.mousemove(event); + + //mouse cursor + var mouse = this.get_mouse_info(event); + var params = this.getParams(); + this.show_mouse_cursor(mouse.x, mouse.y, params.size, 'circle'); + } + + mousedown(e) { + this.started = false; + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.click_valid == false) { + return; + } + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + if (config.layer.rotate || 0 > 0) { + alertify.error('Erase on rotate object is disabled. Please rasterize first.'); + return; + } + this.started = true; + + //get canvas from layer + this.tmpCanvas = document.createElement('canvas'); + this.tmpCanvasCtx = this.tmpCanvas.getContext("2d"); + this.tmpCanvas.width = config.layer.width_original; + this.tmpCanvas.height = config.layer.height_original; + this.tmpCanvasCtx.drawImage(config.layer.link, 0, 0); + + //do blur + this.blur_general('click', mouse, params.size, params.strength); + + //register tmp canvas for faster redraw + config.layer.link_canvas = this.tmpCanvas; + config.need_render = true; + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + if (this.started == false) { + return; + } + + //do blur + this.blur_general('move', mouse, params.size, params.strength); + + //draw draft preview + config.need_render = true; + } + + mouseup(e) { + if (this.started == false) { + return; + } + delete config.layer.link_canvas; + + app.State.do_action( + new app.Actions.Bundle_action('blur_tool', 'Blur Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); + + //decrease memory + this.tmpCanvas.width = 1; + this.tmpCanvas.height = 1; + this.tmpCanvas = null; + this.tmpCanvasCtx = null; + } + + blur_general(type, mouse, size, strength) { + var ctx = this.tmpCanvasCtx; + var mouse_x = Math.round(mouse.x) - config.layer.x; + var mouse_y = Math.round(mouse.y) - config.layer.y; + + //adapt to origin size + mouse_x = this.adaptSize(mouse_x, 'width'); + mouse_y = this.adaptSize(mouse_y, 'height'); + var size_w = this.adaptSize(size, 'width'); + var size_h = this.adaptSize(size, 'height'); + + //find center + var center_x = mouse_x - Math.round(size_w / 2); + var center_y = mouse_y - Math.round(size_h / 2); + + //convert float coords to integers + center_x = Math.round(center_x); + center_y = Math.round(center_y); + mouse_x = Math.round(mouse_x); + mouse_y = Math.round(mouse_y); + + if (type == 'move') { + strength = strength / 2; + if (strength < 1) + strength = 1; + } + + var imageData = ctx.getImageData(center_x, center_y, size_w, size_h); + var filtered = ImageFilters.StackBlur(imageData, strength); //add effect + this.Helper.image_round(this.tmpCanvasCtx, mouse_x, mouse_y, size_w, size_h, filtered); + } + +} +export default Blur_class; diff --git a/frontend/src/js/tools/brush.js b/frontend/src/js/tools/brush.js new file mode 100644 index 0000000..f27ba8a --- /dev/null +++ b/frontend/src/js/tools/brush.js @@ -0,0 +1,570 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; + +class Brush_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.name = 'brush'; + this.layer = {}; + this.params_hash = false; + this.pressure_supported = false; + this.pointer_pressure = 0; // has range [0 - 1] + this.max_speed = 20; + this.power = 2; //how speed affects size + this.event_links = []; + this.data_index = 0; + } + + load() { + var _this = this; + var is_touch = false; + + //pointer events + document.addEventListener('pointerdown', function (event) { + _this.pointerdown(event); + }); + document.addEventListener('pointermove', function (event) { + _this.pointermove(event); + }); + + //mouse events + document.addEventListener('mousedown', function (event) { + if(is_touch) + return; + _this.dragStart(event); + }); + document.addEventListener('mousemove', function (event) { + if(is_touch) + return; + _this.dragMove(event); + }); + document.addEventListener('mouseup', function (event) { + if(is_touch) + return; + _this.dragEnd(event); + }); + + // collect touch events + document.addEventListener('touchstart', function (event) { + is_touch = true; + _this.dragStart(event); + }); + document.addEventListener('touchmove', function (event) { + _this.dragMove(event); + }); + document.addEventListener('touchend', function (event) { + _this.dragEnd(event); + }); + } + + pointerdown(e) { + // Devices that don't actually support pen pressure can give 0.5 as a false reading. + // It is highly unlikely a real pen will read exactly 0.5 at the start of a stroke. + if (e.pressure && e.pressure !== 0 && e.pressure !== 0.5 && e.pressure <= 1) { + this.pressure_supported = true; + this.pointer_pressure = e.pressure; + } else { + this.pressure_supported = false; + } + } + + pointermove(e) { + // Pressure of exactly 1 seems to be an input error, sometimes I see it when lifting the pen + // off the screen when pressure reading should be near 0. + if (this.pressure_supported && e.pressure < 1) { + this.pointer_pressure = e.pressure; + } + } + + dragStart(event) { + var _this = this; + if (config.TOOL.name != _this.name) + return; + this.click_counter++; + + var mouse = this.get_mouse_info(event); + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + + var events = []; + if (event.changedTouches) { + events = event.changedTouches; + } + else{ + events.push(event); + } + for(var i = 0; i < events.length; i++){ + var identifier = null; + if(typeof events[i].identifier != "undefined") { + identifier = events[i].identifier; + } + + this.event_links.push({ + identifier: identifier, + index: this.data_index, + }); + + _this.mousedown_action(events[i], this.data_index, identifier); + + this.data_index++; + } + } + + dragMove(event) { + var _this = this; + if (config.TOOL.name != _this.name) + return; + + if (typeof event.changedTouches == "undefined") { + //mouse cursor + var mouse = _this.get_mouse_info(event); + var params = _this.getParams(); + _this.show_mouse_cursor(mouse.x, mouse.y, params.size, 'circle'); + } + + var mouse = this.get_mouse_info(event); + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + + var events = []; + if (event.changedTouches) { + events = event.changedTouches; + } + else{ + events.push(event); + } + for(var i = 0; i < events.length; i++){ + var identifier = null; + if(typeof events[i].identifier != "undefined") { + identifier = events[i].identifier; + } + + for(var j = 0; i < this.event_links.length; j++){ + if(this.event_links[j].identifier == identifier){ + //found link + _this.mousemove_action(events[i], this.event_links[j].index); + break; + } + } + } + } + + dragEnd(event) { + var _this = this; + if (config.TOOL.name != _this.name) + return; + + var mouse = this.get_mouse_info(event); + if (mouse.click_valid == false) { + return; + } + + var events = []; + if (event.changedTouches) { + events = event.changedTouches; + } + else{ + events.push(event); + } + for(var i = 0; i < events.length; i++){ + var identifier = null; + if(typeof events[i].identifier != "undefined") { + //unlink + identifier = events[i].identifier; + } + + for(var j = 0; i < this.event_links.length; j++){ + if(this.event_links[j].identifier == identifier){ + this.event_links.splice(j, 1); + break; + } + } + + _this.mouseup_action(events[i]); + } + } + + mousedown_action(e, index, event_identifier) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) + return; + + var params_hash = this.get_params_hash(); + + if (config.layer.type != this.name || params_hash != this.params_hash) { + //register new object - current layer is not ours or params changed + this.layer = { + type: this.name, + data: [[]], + params: this.clone(this.getParams()), + status: 'draft', + render_function: [this.name, 'render'], + x: 0, + y: 0, + width: config.WIDTH, + height: config.HEIGHT, + hide_selection_if_active: true, + rotate: null, + is_vector: true, + color: config.COLOR + }; + app.State.do_action( + new app.Actions.Bundle_action('new_brush_layer', 'New Brush Layer', [ + new app.Actions.Insert_layer_action(this.layer) + ]) + ); + this.params_hash = params_hash; + + //reset event links index + this.data_index = 0; + index = 0; + this.event_links = []; + this.event_links.push({ + identifier: event_identifier, + index: this.data_index, + }); + } + else { + const new_data = JSON.parse(JSON.stringify(config.layer.data)); + new_data.push([]); + app.State.do_action( + new app.Actions.Bundle_action('update_brush_layer', 'Update Brush Layer', [ + new app.Actions.Update_layer_action(config.layer.id, { + data: new_data + }) + ]) + ); + } + + //in case of undo, recalculate index + for(var i = index; i >= 0; i++){ + if(typeof config.layer.data[index] != "undefined"){ + break; + } + index--; + } + + var current_group = config.layer.data[index]; + var params = this.getParams(); + + //detect line size + var size = params.size; + var new_size = size; + + if (params.pressure == true) { + if (this.pressure_supported) { + new_size = size * this.pointer_pressure * 2; + } + else { + new_size = size + size / this.max_speed * mouse.speed_average * this.power; + new_size = Math.max(new_size, size / 4); + new_size = Math.round(new_size); + } + } + + var mouse_coords = this.get_mouse_coordinates_from_event(e); + var mouse_x = mouse_coords.x; + var mouse_y = mouse_coords.y; + + current_group.push([mouse_x - config.layer.x, mouse_y - config.layer.y, new_size]); + this.Base_layers.render(); + } + + mousemove_action(e, index) { + var mouse = this.get_mouse_info(e); + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + + //in case of undo, recalculate index + for(var i = index; i >= 0; i++){ + if(typeof config.layer.data[index] != "undefined"){ + break; + } + index--; + } + + var params = this.getParams(); + var current_group = config.layer.data[index]; + + //detect line size + var size = params.size; + var new_size = size; + + if (params.pressure == true) { + if (this.pressure_supported) { + new_size = size * this.pointer_pressure * 2; + } + else { + new_size = size + size / this.max_speed * mouse.speed_average * this.power; + new_size = Math.max(new_size, size / 4); + new_size = Math.round(new_size); + } + } + + var mouse_coords = this.get_mouse_coordinates_from_event(e); + var mouse_x = mouse_coords.x; + var mouse_y = mouse_coords.y; + + current_group.push([mouse_x - config.layer.x, mouse_y - config.layer.y, new_size]); + config.layer.status = 'draft'; + this.Base_layers.render(); + } + + mouseup_action(e, index) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) { + config.layer.status = null; + return; + } + + config.layer.status = null; + + this.check_dimensions(); + this.Base_layers.render(); + } + + render(ctx, layer) { + if (layer.data.length == 0) + return; + + var params = layer.params; + var size = params.size; + + //set styles + ctx.save(); + ctx.fillStyle = layer.color; + ctx.strokeStyle = layer.color; + ctx.lineWidth = params.size; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + + ctx.translate(layer.x, layer.y); + + var data = layer.data; + + //check for legacy format + data = this.check_legacy_format(data); + + var n = data.length; + for (var k = 0; k < n; k++) { + var group_data = data[k]; //data from mouse down till mouse release + var group_n = group_data.length; + + if (params.pressure == false) { + //stabilized lines method does not support multiple line sizes + this.render_stabilized(ctx, group_data); + } + else { + if (group_data[0]) { + ctx.beginPath(); + ctx.moveTo(group_data[0][0], group_data[0][1]); + for (var i = 1; i < group_n; i++) { + if (group_data[i] === null) { + //break + ctx.beginPath(); + } + else { + //line + + ctx.lineWidth = group_data[i][2]; + + if (group_data[i - 1] == null && group_data[i + 1] == null) { + //exception - point + ctx.arc(group_data[i][0], group_data[i][1], size / 2, 0, 2 * Math.PI, false); + ctx.fill(); + } + else if (group_data[i - 1] != null) { + //lines + ctx.lineWidth = group_data[i][2]; + ctx.beginPath(); + ctx.moveTo(group_data[i - 1][0], group_data[i - 1][1]); + ctx.lineTo(group_data[i][0], group_data[i][1]); + ctx.stroke(); + } + } + } + if (group_data[1] == null) { + //point + ctx.beginPath(); + ctx.arc(group_data[0][0], group_data[0][1], size / 2, 0, 2 * Math.PI, false); + ctx.fill(); + } + } + } + } + + ctx.translate(-layer.x, -layer.y); + ctx.restore(); + } + + /** + * draw stabilized lines + * author: Manoj Verma + * source: https://stackoverflow.com/questions/7891740/drawing-smooth-lines-with-canvas/44810470#44810470 + * + * @param ctx + * @param queue + */ + render_stabilized(ctx, queue) { + var data = JSON.parse(JSON.stringify(queue)); + var n = data.length; + + if (data.length == 1) { + //point + var point = data[0]; + ctx.beginPath(); + ctx.arc(point[0], point[1], point[2] / 2, 0, 2 * Math.PI, false); + ctx.fill(); + return; + } + else if (data.length <= 5) { + //not enough points yet + + for (var i = 1; i < n; i++) { + ctx.beginPath(); + ctx.moveTo(data[i - 1][0], data[i - 1][1]); + ctx.lineTo(data[i][0], data[i][1]); + ctx.stroke(); + } + return; + } + + //fix for loose ending, so lets duplicate last point + data.push([data[n - 1][0], data[n - 1][1]]); + + ctx.beginPath(); + ctx.moveTo(data[0][0], data[0][1]); + + //prepare + var temp_data1 = [data[0]]; + var c, d; + for (var i = 1; i < data.length - 1; i = i+1) { + c = (data[i][0] + data[i + 1][0]) / 2; + d = (data[i][1] + data[i + 1][1]) / 2; + temp_data1.push([c, d]); + } + + var temp_data2 = [temp_data1[0]]; + for (var i = 1; i < temp_data1.length - 1; i = i+1) { + c = (temp_data1[i][0] + temp_data1[i + 1][0]) / 2; + d = (temp_data1[i][1] + temp_data1[i + 1][1]) / 2; + temp_data2.push([c, d]); + } + + var temp_data = [temp_data2[0]]; + for (var i = 1; i < temp_data2.length - 1; i = i+1) { + c = (temp_data2[i][0] + temp_data2[i + 1][0]) / 2; + d = (temp_data2[i][1] + temp_data2[i + 1][1]) / 2; + temp_data.push([c, d]); + } + + //draw + for (var i = 1; i < temp_data.length - 2; i = i+1) { + c = (temp_data[i][0] + temp_data[i + 1][0]) / 2; + d = (temp_data[i][1] + temp_data[i + 1][1]) / 2; + ctx.quadraticCurveTo(temp_data[i][0], temp_data[i][1], c, d); + } + + // For the last 2 points + ctx.quadraticCurveTo( + temp_data[i][0], + temp_data[i][1], + temp_data[i+1][0], + temp_data[i+1][1] + ); + ctx.stroke(); + } + + check_legacy_format(data) { + //check for legacy format + if(data.length > 0 && typeof data[0][0] == "number"){ + //convert + var legacy = JSON.parse(JSON.stringify(data)); + data = []; + data.push([]); + var group_index = 0; + for(var i in legacy){ + if(legacy[i] === null){ + data.push([]); + group_index++; + } + else { + data[group_index].push([legacy[i][0], legacy[i][1], legacy[i][2]]); + } + } + } + + return data; + } + + /** + * recalculate layer x, y, width and height values. + */ + check_dimensions() { + var data = JSON.parse(JSON.stringify(config.layer.data)); // Deep copy for history + this.check_legacy_format(data); + + if(config.layer.data.length == 0 || data[0].length == 0) + return; + + //find bounds + var min_x = data[0][0][0]; + var min_y = data[0][0][1]; + var max_x = data[0][0][0]; + var max_y = data[0][0][1]; + + var n = data.length; + for (var k = 0; k < n; k++) { + var group_data = data[k]; + var group_n = group_data.length; + + for (var i = 1; i < group_n; i++) { + min_x = Math.min(min_x, group_data[i][0]); + min_y = Math.min(min_y, group_data[i][1]); + max_x = Math.max(max_x, group_data[i][0]); + max_y = Math.max(max_y, group_data[i][1]); + } + } + + //move current data + for (var k = 0; k < n; k++) { + var group_data = data[k]; + var group_n = group_data.length; + + for (var i = 0; i < group_n; i++) { + group_data[i][0] = group_data[i][0] - min_x; + group_data[i][1] = group_data[i][1] - min_y; + } + } + + //change layers bounds + app.State.do_action( + new app.Actions.Update_layer_action(config.layer.id, { + x: config.layer.x + min_x, + y: config.layer.y + min_y, + width: max_x - min_x, + height: max_y - min_y, + data + }), + { + merge_with_history: ['new_brush_layer', 'update_brush_layer'] + } + ); + } + +} + +export default Brush_class; diff --git a/frontend/src/js/tools/bulge_pinch.js b/frontend/src/js/tools/bulge_pinch.js new file mode 100644 index 0000000..d7da4c4 --- /dev/null +++ b/frontend/src/js/tools/bulge_pinch.js @@ -0,0 +1,118 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; +import glfx from './../libs/glfx.js'; +import Helper_class from './../libs/helpers.js'; + +class BulgePinch_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.fx_filter = false; + this.Helper = new Helper_class(); + this.ctx = ctx; + this.name = 'bulge_pinch'; + this.tmpCanvas = null; + this.tmpCanvasCtx = null; + this.started = false; + } + + load() { + this.default_events(); + } + + default_dragMove(event) { + if (config.TOOL.name != this.name) + return; + + //mouse cursor + var mouse = this.get_mouse_info(event); + var params = this.getParams(); + this.show_mouse_cursor(mouse.x, mouse.y, params.radius, 'circle'); + } + + mousedown(e) { + this.started = false; + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.click_valid == false) { + return; + } + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + this.started = true; + + //get canvas from layer + this.tmpCanvas = document.createElement('canvas'); + this.tmpCanvasCtx = this.tmpCanvas.getContext("2d"); + this.tmpCanvas.width = config.layer.width_original; + this.tmpCanvas.height = config.layer.height_original; + this.tmpCanvasCtx.drawImage(config.layer.link, 0, 0); + + //apply + this.bulgePinch_general(mouse, params.power, params.radius, params.bulge); + + //register tmp canvas for faster redraw + config.layer.link_canvas = this.tmpCanvas; + config.need_render = true; + } + + mouseup(e) { + if (this.started == false) { + return; + } + delete config.layer.link_canvas; + + app.State.do_action( + new app.Actions.Bundle_action('bulge_pinch_tool', 'Bulge/Pinch Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); + + //decrease memory + this.tmpCanvas.width = 1; + this.tmpCanvas.height = 1; + this.tmpCanvas = null; + this.tmpCanvasCtx = null; + } + + bulgePinch_general(mouse, power, radius, bulge) { + if (this.fx_filter == false) { + //init glfx lib + this.fx_filter = glfx.canvas(); + } + + var ctx = this.tmpCanvasCtx; + var mouse_x = Math.round(mouse.x) - config.layer.x; + var mouse_y = Math.round(mouse.y) - config.layer.y; + + //adapt to origin size + mouse_x = this.adaptSize(mouse_x, 'width'); + mouse_y = this.adaptSize(mouse_y, 'height'); + + //convert float coords to integers + mouse_x = Math.round(mouse_x); + mouse_y = Math.round(mouse_y); + + power = power / 100; + if (power > 1) { + //max 100% + power = 1; + } + + if (bulge == false) + power = -1 * power; + + var texture = this.fx_filter.texture(this.tmpCanvas); + this.fx_filter.draw(texture).bulgePinch(mouse_x, mouse_y, radius, power).update(); //effect + this.tmpCanvasCtx.clearRect(0, 0, this.tmpCanvas.width, this.tmpCanvas.height); + this.tmpCanvasCtx.drawImage(this.fx_filter, 0, 0); + } + +} +export default BulgePinch_class; diff --git a/frontend/src/js/tools/clone.js b/frontend/src/js/tools/clone.js new file mode 100644 index 0000000..74694fe --- /dev/null +++ b/frontend/src/js/tools/clone.js @@ -0,0 +1,335 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import Layer_raster_class from './../modules/layer/raster.js'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Clone_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.Layer_raster = new Layer_raster_class(); + this.ctx = ctx; + this.name = 'clone'; + this.tmpCanvas = null; + this.tmpCanvasCtx = null; + this.started = false; + this.clone_coords = null; + this.pressTimer = null; + } + + load() { + var _this = this; + var is_touch = false; + + //mouse events + document.addEventListener('mousedown', function (event) { + if(is_touch) + return; + _this.dragStart(event); + }); + document.addEventListener('mousemove', function (event) { + if(is_touch) + return; + _this.dragMove(event); + }); + document.addEventListener('mouseup', function (event) { + if(is_touch) + return; + _this.dragEnd(event); + }); + + // collect touch events + document.addEventListener('touchstart', function (event) { + is_touch = true; + _this.dragStart(event); + }); + document.addEventListener('touchmove', function (event) { + _this.dragMove(event); + }); + document.addEventListener('touchend', function (event) { + _this.dragEnd(event); + }); + + document.addEventListener('contextmenu', function (event) { + _this.mouseRightClick(event); + }); + } + + dragStart(event) { + var _this = this; + if (config.TOOL.name != _this.name) + return; + _this.mousedown(event); + + var mouse = this.get_mouse_info(event); + if (mouse.click_valid == true) { + this.pressTimer = window.setTimeout(function() { + //long press success + _this.mouseLongClick(); + }, 2000); + } + } + + dragMove(event) { + var _this = this; + if (config.TOOL.name != _this.name) + return; + _this.mousemove(event); + + //mouse cursor + var mouse = _this.get_mouse_info(event); + var params = _this.getParams(); + _this.show_mouse_cursor(mouse.x, mouse.y, params.size, 'circle'); + + clearTimeout(this.pressTimer); + } + + dragEnd(event) { + var _this = this; + if (config.TOOL.name != _this.name) + return; + _this.mouseup(event); + + clearTimeout(this.pressTimer); + } + + on_params_update() { + var params = this.getParams(); + var strict_element = document.getElementById('strict'); + + if (params.circle == false) { + //hide strict controls + strict_element.style.display = 'none'; + } + else { + //show strict controls + strict_element.style.display = 'block'; + } + } + + mouseRightClick(e) { + if (config.TOOL.name != this.name) + return; + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + + if (e.which == 3 && mouse.valid == true) { + e.preventDefault(); + } + if (params.source_layer.value == 'Previous' && config.layer.type === null) { + this.Layer_raster.raster(); + } + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + if (config.layer.rotate || 0 > 0) { + alertify.error('Erase on rotate object is disabled. Please rasterize first.'); + return; + } + if (e.which == 3 && mouse.valid == true) { + //right click - save coords + + var mouse_x = this.adaptSize(mouse.x, 'width'); + var mouse_y = this.adaptSize(mouse.y, 'height'); + + this.clone_coords = { + x: mouse_x, + y: mouse_y, + }; + alertify.success('Source coordinates saved.'); + } + } + + mouseLongClick(){ + var params = this.getParams(); + var mouse = this.get_mouse_info(); + + if (params.source_layer.value == 'Previous' && config.layer.type === null) { + this.Layer_raster.raster(); + } + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + if (config.layer.rotate || 0 > 0) { + alertify.error('Erase on rotate object is disabled. Please rasterize first.'); + return; + } + + var mouse_x = this.adaptSize(mouse.x, 'width'); + var mouse_y = this.adaptSize(mouse.y, 'height'); + + this.clone_coords = { + x: mouse_x, + y: mouse_y, + }; + alertify.success('Source coordinates saved.'); + } + + mousedown(e) { + this.started = false; + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + var layer = config.layer; + var previous_layer = this.Base_layers.find_previous(config.layer.id); + + if (mouse.click_valid == false) { + return; + } + + if (params.source_layer.value == 'Previous' && config.layer.type === null) { + this.Layer_raster.raster(); + } + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + if (config.layer.rotate || 0 > 0) { + alertify.error('Erase on rotate object is disabled. Please rasterize first.'); + return; + } + if (this.clone_coords === null) { + alertify.error('Source is empty, right click on image or use long press to save source position.'); + return; + } + if (layer.width != layer.width_original || layer.height != layer.height_original) { + alertify.error('Clone tool disabled for resized image. Please rasterize first.'); + return; + } + if (params.source_layer.value == 'Previous' && + (previous_layer.width != previous_layer.width_original + || previous_layer.height != previous_layer.height_original)) { + alertify.error('Clone tool disabled for resized image. Please rasterize first.'); + return; + } + if (params.source_layer.value == 'Previous') { + if (previous_layer == null) { + alertify.error('Can not find previous layer.'); + return; + } + if (previous_layer.type != 'image') { + alertify.error('Previous layer must be image, convert it to raster to apply this tool.'); + return; + } + } + this.started = true; + + //get canvas from layer + this.tmpCanvas = document.createElement('canvas'); + this.tmpCanvasCtx = this.tmpCanvas.getContext("2d"); + this.tmpCanvas.width = config.layer.width_original; + this.tmpCanvas.height = config.layer.height_original; + this.tmpCanvasCtx.drawImage(config.layer.link, 0, 0); + + //clone + this.clone_general(this.tmpCanvas, this.tmpCanvas, 'click', mouse); + + //register tmp canvas for progress redraw + config.layer.link_canvas = this.tmpCanvas; + config.need_render = true; + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + if (this.started == false) { + return; + } + + //clone + this.clone_general(this.tmpCanvas, this.tmpCanvas, 'move', mouse); + + //draw draft preview + config.need_render = true; + } + + mouseup(e) { + if (this.started == false) { + return; + } + delete config.layer.link_canvas; + + app.State.do_action( + new app.Actions.Bundle_action('clone_tool', 'Clone Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); + + //decrease memory + this.tmpCanvas.width = 1; + this.tmpCanvas.height = 1; + this.tmpCanvas = null; + this.tmpCanvasCtx = null; + } + + clone_general(canvas_from, canvas_to, type, mouse) { + var params = this.getParams(); + + var mouse_x = Math.round(mouse.x) - config.layer.x; + var mouse_y = Math.round(mouse.y) - config.layer.y; + var half = Math.round(params.size / 2); + + //adapt to origin size + mouse_x = this.adaptSize(mouse_x, 'width'); + mouse_y = this.adaptSize(mouse_y, 'height'); + + //convert float coords to integers + mouse_x = Math.round(mouse_x); + mouse_y = Math.round(mouse_y); + + //create source canvas + var canvas_source = document.createElement("canvas"); + var ctx_source = canvas_source.getContext("2d"); + var w = Math.ceil(params.size); + var h = Math.ceil(params.size); + canvas_source.width = w; + canvas_source.height = h; + + //add data + var x_from = Math.round(this.clone_coords.x - (mouse.click_x - mouse_x)); + var y_from = Math.round(this.clone_coords.y - (mouse.click_y - mouse_y)); + if (params.anti_aliasing == false) { + ctx_source.arc(half, half, half, 0, Math.PI * 2, false); + ctx_source.clip(); + } + if (params.source_layer.value == 'Previous') { + var previous_layer = this.Base_layers.find_previous(config.layer.id); + + x_from = Math.round(this.clone_coords.x - (mouse.click_x - mouse_x)) - previous_layer.x + config.layer.x; + y_from = Math.round(this.clone_coords.y - (mouse.click_y - mouse_y)) - previous_layer.y + config.layer.y; + + ctx_source.drawImage(previous_layer.link, x_from - half, y_from - half, w, h, 0, 0, w, h); + } + else { + ctx_source.drawImage(canvas_from, x_from - half, y_from - half, w, h, 0, 0, w, h); + } + + //apply anti aliasing + if (params.anti_aliasing == true) { + var gradient = ctx_source.createRadialGradient(half, half, 0, half, half, half + 1); + gradient.addColorStop(0, 'white'); + gradient.addColorStop(0.3, 'white'); + gradient.addColorStop(1, 'transparent'); + ctx_source.fillStyle = gradient; + + ctx_source.globalCompositeOperation = 'destination-in'; + ctx_source.fillRect(0, 0, params.size, params.size); + ctx_source.globalCompositeOperation = 'source-over'; + } + + //finish + canvas_to.getContext("2d").drawImage(canvas_source, mouse_x - half, mouse_y - half); + } + +} +export default Clone_class; diff --git a/frontend/src/js/tools/crop.js b/frontend/src/js/tools/crop.js new file mode 100644 index 0000000..fa53f2d --- /dev/null +++ b/frontend/src/js/tools/crop.js @@ -0,0 +1,306 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import GUI_tools_class from './../core/gui/gui-tools.js'; +import Base_gui_class from './../core/base-gui.js'; +import Base_selection_class from './../core/base-selection.js'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Crop_class extends Base_tools_class { + + constructor(ctx) { + super(); + var _this = this; + this.Base_layers = new Base_layers_class(); + this.Base_gui = new Base_gui_class(); + this.GUI_tools = new GUI_tools_class(); + this.ctx = ctx; + this.name = 'crop'; + this.selection = { + x: null, + y: null, + width: null, + height: null, + }; + var sel_config = { + enable_background: true, + enable_borders: true, + enable_controls: true, + crop_lines: true, + enable_rotation: false, + enable_move: false, + data_function: function () { + return _this.selection; + }, + }; + this.mousedown_selection = null; + this.Base_selection = new Base_selection_class(ctx, sel_config, this.name); + } + + load() { + this.default_events(); + } + + default_dragStart(event) { + this.is_mousedown_canvas = false; + if (config.TOOL.name != this.name) + return; + if (!event.target.closest('#main_wrapper')) + return; + + this.is_mousedown_canvas = true; + this.mousedown(event); + } + + mousedown(e) { + var mouse = this.get_mouse_info(e); + if (this.Base_selection.is_drag == false || mouse.click_valid == false) + return; + + this.mousedown_selection = JSON.parse(JSON.stringify(this.selection)); + + if (this.Base_selection.mouse_lock !== null) { + return; + } + + //create new selection + this.Base_selection.set_selection(mouse.x, mouse.y, 0, 0); + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + if (this.Base_selection.is_drag == false || mouse.is_drag == false) { + return; + } + if (e.type == 'mousedown' && mouse.click_valid == false) { + return; + } + if (this.Base_selection.mouse_lock !== null) { + return; + } + + var width = mouse.x - mouse.click_x; + var height = mouse.y - mouse.click_y; + + if(e.ctrlKey == true || e.metaKey){ + //ctrl is pressed - crop will be calculated based on global width and height ratio + var ratio = config.WIDTH / config.HEIGHT; + var width_new = Math.round(height * ratio); + var height_new = Math.round(width / ratio); + + if(Math.abs(width * 100 / width_new) > Math.abs(height * 100 / height_new)){ + if (width * 100 / width_new > 0) + height = height_new; + else + height = -height_new; + } + else{ + if (height * 100 / height_new > 0) + width = width_new; + else + width = -width_new; + } + } + + this.Base_selection.set_selection(null, null, width, height); + } + + mouseup(e) { + var mouse = this.get_mouse_info(e); + + if (!this.Base_selection.is_drag) { + return; + } + if (e.type == 'mousedown' && mouse.click_valid == false) { + return; + } + + var width = mouse.x - this.selection.x; + var height = mouse.y - this.selection.y; + + if (width == 0 || height == 0) { + //cancel selection + this.Base_selection.reset_selection(); + config.need_render = true; + return; + } + + if (this.selection.width != null) { + //make sure coords not negative + var details = this.selection; + var x = details.x; + var y = details.y; + if (details.width < 0) { + x = x + details.width; + } + if (details.height < 0) { + y = y + details.height; + } + this.selection = { + x: x, + y: y, + width: Math.abs(details.width), + height: Math.abs(details.height), + }; + } + + //control boundaries + if (this.selection.x < 0) { + this.selection.width += this.selection.x; + this.selection.x = 0; + } + if (this.selection.y < 0) { + this.selection.height += this.selection.y; + this.selection.y = 0; + } + if (this.selection.x + this.selection.width > config.WIDTH) { + this.selection.width = config.WIDTH - this.selection.x; + } + if (this.selection.y + this.selection.height > config.HEIGHT) { + this.selection.height = config.HEIGHT - this.selection.y; + } + + app.State.do_action( + new app.Actions.Set_selection_action(this.selection.x, this.selection.y, this.selection.width, this.selection.height, this.mousedown_selection) + ); + } + + render(ctx, layer) { + //nothing + } + + /** + * do actual crop + */ + async on_params_update() { + var params = this.getParams(); + var selection = this.selection; + params.crop = true; + this.GUI_tools.show_action_attributes(); + + if (selection.width == null || selection.width == 0 || selection.height == 0) { + alertify.error('Empty selection'); + return; + } + + //check for rotation + var rotated_name = false; + for (var i in config.layers) { + var link = config.layers[i]; + if (link.type == null) + continue; + + if(link.rotate > 0){ + rotated_name = link.name; + break; + } + } + if (rotated_name !== false) { + alertify.error('Crop on rotated layer is not supported. Convert it to raster to continue.' + '('+ rotated_name + ')'); + return; + } + + //controll boundaries + selection.x = Math.max(selection.x, 0); + selection.y = Math.max(selection.y, 0); + selection.width = Math.min(selection.width, config.WIDTH); + selection.height = Math.min(selection.height, config.HEIGHT); + + let actions = []; + + for (var i in config.layers) { + var link = config.layers[i]; + if (link.type == null) + continue; + + let x = link.x; + let y = link.y; + let width = link.width; + let height = link.height; + let width_original = link.width_original; + let height_original = link.height_original; + + //move + x -= parseInt(selection.x); + y -= parseInt(selection.y); + + if (link.type == 'image') { + //also remove unvisible data + let left = 0; + if (x < 0) + left = -x; + let top = 0; + if (y < 0) + top = -y; + let right = 0; + if (x + width > selection.width) + right = x + width - selection.width; + let bottom = 0; + if (y + height > selection.height) + bottom = y + height - selection.height; + let crop_width = width - left - right; + let crop_height = height - top - bottom; + + //if image was streched + let width_ratio = (width / width_original); + let height_ratio = (height / height_original); + + //create smaller canvas + let canvas = document.createElement('canvas'); + let ctx = canvas.getContext("2d"); + canvas.width = crop_width / width_ratio; + canvas.height = crop_height / height_ratio; + + //cut required part + ctx.translate(-left / width_ratio, -top / height_ratio); + canvas.getContext("2d").drawImage(link.link, 0, 0); + ctx.translate(0, 0); + actions.push( + new app.Actions.Update_layer_image_action(canvas, link.id) + ); + + //update attributes + width = Math.ceil(canvas.width * width_ratio); + height = Math.ceil(canvas.height * height_ratio); + x += left; + y += top; + width_original = canvas.width; + height_original = canvas.height; + } + + actions.push( + new app.Actions.Update_layer_action(link.id, { + x, + y, + width, + height, + width_original, + height_original + }) + ); + } + + actions.push( + new app.Actions.Prepare_canvas_action('undo'), + new app.Actions.Update_config_action({ + WIDTH: parseInt(selection.width), + HEIGHT: parseInt(selection.height) + }), + new app.Actions.Prepare_canvas_action('do'), + new app.Actions.Reset_selection_action(this.selection) + ); + await app.State.do_action( + new app.Actions.Bundle_action('crop_tool', 'Crop Tool', actions) + ); + } + + on_leave() { + return [ + new app.Actions.Reset_selection_action() + ]; + } + +} + +export default Crop_class; diff --git a/frontend/src/js/tools/desaturate.js b/frontend/src/js/tools/desaturate.js new file mode 100644 index 0000000..0d06d08 --- /dev/null +++ b/frontend/src/js/tools/desaturate.js @@ -0,0 +1,135 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; +import ImageFilters from './../libs/imagefilters.js'; +import Helper_class from './../libs/helpers.js'; + +class Desaturate_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.ctx = ctx; + this.name = 'desaturate'; + this.tmpCanvas = null; + this.tmpCanvasCtx = null; + this.started = false; + } + + load() { + this.default_events(); + } + + default_dragMove(event) { + if (config.TOOL.name != this.name) + return; + this.mousemove(event); + + //mouse cursor + var mouse = this.get_mouse_info(event); + var params = this.getParams(); + this.show_mouse_cursor(mouse.x, mouse.y, params.size, 'circle'); + } + + mousedown(e) { + this.started = false; + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.click_valid == false) { + return; + } + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + if (config.layer.rotate || 0 > 0) { + alertify.error('Erase on rotate object is disabled. Please rasterize first.'); + return; + } + this.started = true; + + //get canvas from layer + this.tmpCanvas = document.createElement('canvas'); + this.tmpCanvasCtx = this.tmpCanvas.getContext("2d"); + this.tmpCanvas.width = config.layer.width_original; + this.tmpCanvas.height = config.layer.height_original; + + this.tmpCanvasCtx.drawImage(config.layer.link, 0, 0); + + //do desaturate + this.desaturate_general('click', mouse, params.size, params.anti_aliasing); + + //register tmp canvas for faster redraw + config.layer.link_canvas = this.tmpCanvas; + config.need_render = true; + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + if (this.started == false) { + return; + } + + //do desaturate + this.desaturate_general('move', mouse, params.size, params.anti_aliasing); + + //draw draft preview + config.need_render = true; + } + + mouseup(e) { + if (this.started == false) { + return; + } + delete config.layer.link_canvas; + + app.State.do_action( + new app.Actions.Bundle_action('desaturate_tool', 'Desaturate Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); + + //decrease memory + this.tmpCanvas.width = 1; + this.tmpCanvas.height = 1; + this.tmpCanvas = null; + this.tmpCanvasCtx = null; + } + + desaturate_general(type, mouse, size, anti_aliasing) { + var ctx = this.tmpCanvasCtx; + var mouse_x = Math.round(mouse.x) - config.layer.x; + var mouse_y = Math.round(mouse.y) - config.layer.y; + + //adapt to origin size + mouse_x = this.adaptSize(mouse_x, 'width'); + mouse_y = this.adaptSize(mouse_y, 'height'); + var size_w = this.adaptSize(size, 'width'); + var size_h = this.adaptSize(size, 'height'); + + //find center + var center_x = mouse_x - Math.round(size_w / 2); + var center_y = mouse_y - Math.round(size_h / 2); + + //convert float coords to integers + center_x = Math.round(center_x); + center_y = Math.round(center_y); + mouse_x = Math.round(mouse_x); + mouse_y = Math.round(mouse_y); + + var imageData = ctx.getImageData(center_x, center_y, size_w, size_h); + var filtered = ImageFilters.GrayScale(imageData); //add effect + this.Helper.image_round(this.tmpCanvasCtx, mouse_x, mouse_y, size_w, size_h, filtered, anti_aliasing); + } + +} +export default Desaturate_class; diff --git a/frontend/src/js/tools/erase.js b/frontend/src/js/tools/erase.js new file mode 100644 index 0000000..6a239e3 --- /dev/null +++ b/frontend/src/js/tools/erase.js @@ -0,0 +1,207 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Erase_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'erase'; + this.tmpCanvas = null; + this.tmpCanvasCtx = null; + this.started = false; + } + + load() { + this.default_events(); + } + + default_dragMove(event, is_touch) { + if (config.TOOL.name != this.name) + return; + this.mousemove(event, is_touch); + + //mouse cursor + var mouse = this.get_mouse_info(event); + var params = this.getParams(); + if (params.circle == true) + this.show_mouse_cursor(mouse.x, mouse.y, params.size, 'circle'); + else + this.show_mouse_cursor(mouse.x, mouse.y, params.size, 'rect'); + } + + on_params_update() { + var params = this.getParams(); + var strict_element = document.querySelector('.attributes #strict'); + + if (params.circle == false) { + //hide strict controls + strict_element.style.display = 'none'; + } + else { + //show strict controls + strict_element.style.display = 'block'; + } + } + + mousedown(e) { + this.started = false; + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.click_valid == false) { + return; + } + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + if (config.layer.is_vector == true) { + alertify.error('Layer is vector, convert it to raster to apply this tool.'); + return; + } + if (config.layer.rotate || 0 > 0) { + alertify.error('Erase on rotate object is disabled. Please rasterize first.'); + return; + } + this.started = true; + + //get canvas from layer + this.tmpCanvas = document.createElement('canvas'); + this.tmpCanvasCtx = this.tmpCanvas.getContext("2d"); + this.tmpCanvas.width = config.layer.width_original; + this.tmpCanvas.height = config.layer.height_original; + this.tmpCanvasCtx.drawImage(config.layer.link, 0, 0); + + this.tmpCanvasCtx.scale( + config.layer.width_original / config.layer.width, + config.layer.height_original / config.layer.height + ); + + //do erase + this.erase_general(this.tmpCanvasCtx, 'click', mouse, params.size, params.strict, params.circle); + + //register tmp canvas for faster redraw + config.layer.link_canvas = this.tmpCanvas; + config.need_render = true; + } + + mousemove(e, is_touch) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + if (this.started == false) { + return; + } + if (mouse.click_x == mouse.x && mouse.click_y == mouse.y) { + //same coordinates + return; + } + + //do erase + this.erase_general(this.tmpCanvasCtx, 'move', mouse, params.size, params.strict, params.circle, is_touch); + + //draw draft preview + config.need_render = true; + } + + mouseup(e) { + if (this.started == false) { + return; + } + delete config.layer.link_canvas; + + app.State.do_action( + new app.Actions.Bundle_action('erase_tool', 'Erase Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); + + //decrease memory + this.tmpCanvas.width = 1; + this.tmpCanvas.height = 1; + this.tmpCanvas = null; + this.tmpCanvasCtx = null; + } + + erase_general(ctx, type, mouse, size, strict, is_circle, is_touch) { + var mouse_x = Math.round(mouse.x) - config.layer.x; + var mouse_y = Math.round(mouse.y) - config.layer.y; + var alpha = config.ALPHA; + var mouse_last_x = parseInt(mouse.last_x) - config.layer.x; + var mouse_last_y = parseInt(mouse.last_y) - config.layer.y; + + ctx.beginPath(); + ctx.lineWidth = size; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + if (alpha < 255) + ctx.strokeStyle = "rgba(255, 255, 255, " + alpha / 255 / 10 + ")"; + else + ctx.strokeStyle = "rgba(255, 255, 255, 1)"; + + if (is_circle == false) { + //rectangle + var size_half = Math.ceil(size / 2); + if (size == 1) { + //single cell mode + mouse_x = Math.floor(mouse.x) - config.layer.x; + mouse_y = Math.floor(mouse.y) - config.layer.y; + size_half = 0; + } + ctx.save(); + ctx.globalCompositeOperation = 'destination-out'; + ctx.fillStyle = "rgba(255, 255, 255, " + alpha / 255 + ")"; + ctx.fillRect(mouse_x - size_half, mouse_y - size_half, size, size); + ctx.restore(); + } + else { + //circle + ctx.save(); + + if (strict == false) { + var radgrad = ctx.createRadialGradient( + mouse_x, mouse_y, size / 8, + mouse_x, mouse_y, size / 2); + if (type == 'click') + radgrad.addColorStop(0, "rgba(255, 255, 255, " + alpha / 255 + ")"); + else if (type == 'move') + radgrad.addColorStop(0, "rgba(255, 255, 255, " + alpha / 255 / 2 + ")"); + radgrad.addColorStop(1, "rgba(255, 255, 255, 0)"); + } + + //set Composite + ctx.globalCompositeOperation = 'destination-out'; + if (strict == true) + ctx.fillStyle = "rgba(255, 255, 255, " + alpha / 255 + ")"; + else + ctx.fillStyle = radgrad; + ctx.beginPath(); + ctx.arc(mouse_x, mouse_y, size / 2, 0, Math.PI * 2, true); + ctx.fill(); + ctx.restore(); + } + + //extra work if mouse moving fast - fill gaps + if (type == 'move' && is_circle == true && mouse_last_x != false && mouse_last_y != false && is_touch !== true) { + ctx.save(); + ctx.globalCompositeOperation = 'destination-out'; + + ctx.beginPath(); + ctx.moveTo(mouse_last_x, mouse_last_y); + ctx.lineTo(mouse_x, mouse_y); + ctx.stroke(); + + ctx.restore(); + } + } + +} +export default Erase_class; diff --git a/frontend/src/js/tools/fill.js b/frontend/src/js/tools/fill.js new file mode 100644 index 0000000..d2bdc4a --- /dev/null +++ b/frontend/src/js/tools/fill.js @@ -0,0 +1,231 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import Helper_class from './../libs/helpers.js'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Fill_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.ctx = ctx; + this.name = 'fill'; + this.working = false; + } + + dragStart(event) { + var _this = this; + if (config.TOOL.name != _this.name) + return; + _this.mousedown(event); + } + + load() { + var _this = this; + + //mouse events + document.addEventListener('mousedown', function (event) { + _this.dragStart(event); + }); + + // collect touch events + document.addEventListener('touchstart', function (event) { + _this.dragStart(event); + }); + } + + mousedown(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) { + return; + } + if (config.layer.rotate || 0 > 0) { + alertify.error('Erase on rotate object is disabled. Please rasterize first.'); + return; + } + + this.fill(mouse); + } + + async fill(mouse) { + var params = this.getParams(); + + if(this.working == true){ + return; + } + + if (config.layer.type != 'image' && config.layer.type !== null) { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + if (config.layer.is_vector == true) { + alertify.error('Layer is vector, convert it to raster to apply this tool.'); + return; + } + if (config.ALPHA == 0) { + alertify.error('Color alpha value can not be zero.'); + return; + } + + //get canvas from layer + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext("2d"); + if (config.layer.type !== null) { + canvas.width = config.layer.width_original; + canvas.height = config.layer.height_original; + ctx.drawImage(config.layer.link, 0, 0); + } + else { + canvas.width = config.WIDTH; + canvas.height = config.HEIGHT; + } + + var mouse_x = Math.round(mouse.x) - config.layer.x; + var mouse_y = Math.round(mouse.y) - config.layer.y; + + //adapt to origin size + mouse_x = this.adaptSize(mouse_x, 'width'); + mouse_y = this.adaptSize(mouse_y, 'height'); + + //convert float coords to integers + mouse_x = Math.round(mouse_x); + mouse_y = Math.round(mouse_y); + + var color_to = this.Helper.hexToRgb(config.COLOR); + color_to.a = config.ALPHA; + + //change + this.working = true; + this.fill_general(ctx, config.WIDTH, config.HEIGHT, + mouse_x, mouse_y, color_to, params.power, params.anti_aliasing, params.contiguous); + + if (config.layer.type != null) { + //update + app.State.do_action( + new app.Actions.Bundle_action('fill_tool', 'Fill Tool', [ + new app.Actions.Update_layer_image_action(canvas) + ]) + ); + } + else { + //create new + var params = []; + params.type = 'image'; + params.name = 'Fill'; + params.data = canvas.toDataURL("image/png"); + params.x = parseInt(canvas.dataset.x) || 0; + params.y = parseInt(canvas.dataset.y) || 0; + params.width = canvas.width; + params.height = canvas.height; + app.State.do_action( + new app.Actions.Bundle_action('fill_tool', 'Fill Tool', [ + new app.Actions.Insert_layer_action(params) + ]) + ); + } + + //prevent crash bug on touch screen - hard to explain and debug + await new Promise(r => setTimeout(r, 10)); + this.working = false; + } + + fill_general(context, W, H, x, y, color_to, sensitivity, anti_aliasing, contiguous = false) { + sensitivity = sensitivity * 255 / 100; //convert to 0-255 interval + x = parseInt(x); + y = parseInt(y); + var canvasTemp = document.createElement('canvas'); + canvasTemp.width = W; + canvasTemp.height = H; + var ctxTemp = canvasTemp.getContext("2d"); + + ctxTemp.rect(0, 0, W, H); + ctxTemp.fillStyle = "rgba(255, 255, 255, 0)"; + ctxTemp.fill(); + + var img_tmp = ctxTemp.getImageData(0, 0, W, H); + var imgData_tmp = img_tmp.data; + + var img = context.getImageData(0, 0, W, H); + var imgData = img.data; + var k = ((y * (img.width * 4)) + (x * 4)); + var dx = [0, -1, +1, 0]; + var dy = [-1, 0, 0, +1]; + var color_from = { + r: imgData[k + 0], + g: imgData[k + 1], + b: imgData[k + 2], + a: imgData[k + 3] + }; + if (color_from.r == color_to.r && color_from.g == color_to.g + && color_from.b == color_to.b && color_from.a == color_to.a) { + return false; + } + + if (contiguous == false) { + //check only nearest pixels + var stack = []; + stack.push([x, y]); + while (stack.length > 0) { + var curPoint = stack.pop(); + for (var i = 0; i < 4; i++) { + var nextPointX = curPoint[0] + dx[i]; + var nextPointY = curPoint[1] + dy[i]; + if (nextPointX < 0 || nextPointY < 0 || nextPointX >= W || nextPointY >= H) + continue; + var k = (nextPointY * W + nextPointX) * 4; + if (imgData_tmp[k + 3] != 0) + continue; //already parsed + + //check + if (Math.abs(imgData[k + 0] - color_from.r) <= sensitivity && + Math.abs(imgData[k + 1] - color_from.g) <= sensitivity && + Math.abs(imgData[k + 2] - color_from.b) <= sensitivity && + Math.abs(imgData[k + 3] - color_from.a) <= sensitivity) { + + //fill pixel + imgData_tmp[k] = color_to.r; //r + imgData_tmp[k + 1] = color_to.g; //g + imgData_tmp[k + 2] = color_to.b; //b + imgData_tmp[k + 3] = color_to.a; //a + + stack.push([nextPointX, nextPointY]); + } + } + } + } + else { + //global mode - contiguous + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + + //imgData[i] + 0.7152 * imgData[i + 1] + 0.0722 * imgData[i + 2]); + + for (var j = 0; j < 4; j++) { + var k = i + j; + + if (Math.abs(imgData[k] - color_from.r) <= sensitivity + && Math.abs(imgData[k + 1] - color_from.g) <= sensitivity + && Math.abs(imgData[k + 2] - color_from.b) <= sensitivity + && Math.abs(imgData[k + 3] - color_from.a) <= sensitivity) { + imgData_tmp[k] = color_to.r; //r + imgData_tmp[k + 1] = color_to.g; //g + imgData_tmp[k + 2] = color_to.b; //b + imgData_tmp[k + 3] = color_to.a; //a + } + } + } + } + + ctxTemp.putImageData(img_tmp, 0, 0); + if (anti_aliasing == true) { + context.filter = 'blur(1px)'; + } + context.drawImage(canvasTemp, 0, 0); + } + +} +export default Fill_class; diff --git a/frontend/src/js/tools/gradient.js b/frontend/src/js/tools/gradient.js new file mode 100644 index 0000000..0a1008c --- /dev/null +++ b/frontend/src/js/tools/gradient.js @@ -0,0 +1,184 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import Helper_class from './../libs/helpers.js'; + +class Gradient_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.ctx = ctx; + this.name = 'gradient'; + this.layer = {}; + } + + load() { + this.default_events(); + } + + mousedown(e) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.click_valid == false) + return; + + var name = this.name; + var is_vector = false; + if (params.radial == true) { + name = 'Radial gradient'; + is_vector = true; + } + + //register new object - current layer is not ours or params changed + this.layer = { + type: this.name, + name: this.Helper.ucfirst(name) + ' #' + this.Base_layers.auto_increment, + params: this.clone(this.getParams()), + status: 'draft', + render_function: [this.name, 'render'], + x: mouse.x, + y: mouse.y, + rotate: null, + is_vector: is_vector, + color: null, + data: { + center_x: mouse.x, + center_y: mouse.y, + }, + }; + app.State.do_action( + new app.Actions.Bundle_action('new_gradient_layer', 'New Gradient Layer', [ + new app.Actions.Insert_layer_action(this.layer) + ]) + ); + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + + var width = mouse.x - this.layer.x; + var height = mouse.y - this.layer.y; + + if (params.radial == true) { + config.layer.x = this.layer.data.center_x - width; + config.layer.y = this.layer.data.center_y - height; + config.layer.width = width * 2; + config.layer.height = height * 2; + } + else { + config.layer.width = width; + config.layer.height = height; + } + + this.Base_layers.render(); + } + + mouseup(e) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.click_valid == false) { + config.layer.status = null; + return; + } + + var width = mouse.x - this.layer.x; + var height = mouse.y - this.layer.y; + + if (width == 0 && height == 0) { + //same coordinates - cancel + app.State.scrap_last_action(); + return; + } + + let new_settings = {}; + if (params.radial == true) { + new_settings = { + x: this.layer.data.center_x - width, + y: this.layer.data.center_y - height, + width: width * 2, + height: height * 2 + } + } + else { + new_settings = { + width, + height + } + } + new_settings.status = null; + + app.State.do_action( + new app.Actions.Update_layer_action(config.layer.id, new_settings), + { merge_with_history: 'new_gradient_layer' } + ); + + this.Base_layers.render(); + } + + render(ctx, layer) { + if (layer.width == 0 && layer.height == 0) + return; + + var params = layer.params; + var power = params.radial_power; + if(power > 99){ + power = 99; + } + var alpha = params.alpha / 100 * 255; + if(power > 255){ + power = 255; + } + + var color1 = params.color_1; + var color2 = params.color_2; + var radial = params.radial; + + var color2_rgb = this.Helper.hexToRgb(color2); + + var width = layer.x + layer.width - 1; + var height = layer.y + layer.height - 1; + + if (radial == false) { + //linear + ctx.beginPath(); + ctx.rect(0, 0, config.WIDTH, config.HEIGHT); + var grd = ctx.createLinearGradient( + layer.x, layer.y, + width, height); + + grd.addColorStop(0, color1); + grd.addColorStop(1, "rgba(" + color2_rgb.r + ", " + color2_rgb.g + ", " + + color2_rgb.b + ", " + alpha / 255 + ")"); + ctx.fillStyle = grd; + ctx.fill(); + } + else { + //radial + var dist_x = layer.width; + var dist_y = layer.height; + var center_x = layer.x + Math.round(layer.width / 2); + var center_y = layer.y + Math.round(layer.height / 2); + var distance = Math.sqrt((dist_x * dist_x) + (dist_y * dist_y)); + var radgrad = ctx.createRadialGradient( + center_x, center_y, distance * power / 100, + center_x, center_y, distance); + + radgrad.addColorStop(0, color1); + radgrad.addColorStop(1, "rgba(" + color2_rgb.r + ", " + color2_rgb.g + ", " + + color2_rgb.b + ", " + alpha / 255 + ")"); + ctx.fillStyle = radgrad; + ctx.fillRect(0, 0, config.WIDTH, config.HEIGHT); + } + } + +} +export default Gradient_class; diff --git a/frontend/src/js/tools/magic_erase.js b/frontend/src/js/tools/magic_erase.js new file mode 100644 index 0000000..6a1c891 --- /dev/null +++ b/frontend/src/js/tools/magic_erase.js @@ -0,0 +1,214 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Magic_erase_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'magic_erase'; + this.working = false; + } + + dragStart(event) { + var _this = this; + if (config.TOOL.name != _this.name) + return; + _this.mousedown(event); + } + + load() { + var _this = this; + + //mouse events + document.addEventListener('mousedown', function (event) { + _this.dragStart(event); + }); + + // collect touch events + document.addEventListener('touchstart', function (event) { + _this.dragStart(event); + }); + } + + mousedown(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) { + return; + } + if (config.layer.rotate || 0 > 0) { + alertify.error('Erase on rotate object is disabled. Please rasterize first.'); + return; + } + + this.magic_erase(mouse); + } + + async magic_erase(mouse) { + var params = this.getParams(); + + if(this.working == true){ + return; + } + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + if (config.layer.is_vector == true) { + alertify.error('Layer is vector, convert it to raster to apply this tool.'); + return; + } + + //get canvas from layer + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext("2d"); + canvas.width = config.layer.width_original; + canvas.height = config.layer.height_original; + ctx.drawImage(config.layer.link, 0, 0); + + var mouse_x = Math.round(mouse.x) - config.layer.x; + var mouse_y = Math.round(mouse.y) - config.layer.y; + + //adapt to origin size + mouse_x = this.adaptSize(mouse_x, 'width'); + mouse_y = this.adaptSize(mouse_y, 'height'); + + //convert float coords to integers + mouse_x = Math.round(mouse_x); + mouse_y = Math.round(mouse_y); + + //change + this.working = true; + this.magic_erase_general(ctx, config.WIDTH, config.HEIGHT, + mouse_x, mouse_y, params.power, params.anti_aliasing, params.contiguous); + + app.State.do_action( + new app.Actions.Bundle_action('magic_erase_tool', 'Magic Eraser Tool', [ + new app.Actions.Update_layer_image_action(canvas) + ]) + ); + //prevent crash bug on touch screen - hard to explain and debug + await new Promise(r => setTimeout(r, 10)); + this.working = false; + } + + /** + * apply magic erase + * + * @param {ctx} context + * @param {int} W + * @param {int} H + * @param {int} x + * @param {int} y + * @param {int} sensitivity max 100 + * @param {Boolean} anti_aliasing + */ + magic_erase_general(context, W, H, x, y, sensitivity, anti_aliasing, contiguous = false) { + sensitivity = sensitivity * 255 / 100; //convert to 0-255 interval + x = parseInt(x); + y = parseInt(y); + var canvasTemp = document.createElement('canvas'); + canvasTemp.width = W; + canvasTemp.height = H; + var ctxTemp = canvasTemp.getContext("2d"); + + ctxTemp.rect(0, 0, W, H); + ctxTemp.fillStyle = "rgba(255, 255, 255, 0)"; + ctxTemp.fill(); + + var img_tmp = ctxTemp.getImageData(0, 0, W, H); + var imgData_tmp = img_tmp.data; + + var img = context.getImageData(0, 0, W, H); + var imgData = img.data; + var k = ((y * (img.width * 4)) + (x * 4)); + var dx = [0, -1, +1, 0]; + var dy = [-1, 0, 0, +1]; + var color_to = { + r: 255, + g: 255, + b: 255, + a: 255 + }; + var color_from = { + r: imgData[k + 0], + g: imgData[k + 1], + b: imgData[k + 2], + a: imgData[k + 3] + }; + if (color_from.r == color_to.r && + color_from.g == color_to.g && + color_from.b == color_to.b && + color_from.a == 0) { + return false; + } + if (contiguous == false) { + //check only nearest pixels + var stack = []; + stack.push([x, y]); + while (stack.length > 0) { + var curPoint = stack.pop(); + for (var i = 0; i < 4; i++) { + var nextPointX = curPoint[0] + dx[i]; + var nextPointY = curPoint[1] + dy[i]; + if (nextPointX < 0 || nextPointY < 0 || nextPointX >= W || nextPointY >= H) + continue; + var k = (nextPointY * W + nextPointX) * 4; + if (imgData_tmp[k + 3] != 0) + continue; //already parsed + + if (Math.abs(imgData[k] - color_from.r) <= sensitivity + && Math.abs(imgData[k + 1] - color_from.g) <= sensitivity + && Math.abs(imgData[k + 2] - color_from.b) <= sensitivity + && Math.abs(imgData[k + 3] - color_from.a) <= sensitivity) { + //erase + imgData_tmp[k] = color_to.r; //r + imgData_tmp[k + 1] = color_to.g; //g + imgData_tmp[k + 2] = color_to.b; //b + imgData_tmp[k + 3] = color_to.a; //a + + stack.push([nextPointX, nextPointY]); + } + } + } + } + else { + //global mode - contiguous + for (var i = 0; i < imgData.length; i += 4) { + if (imgData[i + 3] == 0) + continue; //transparent + + //imgData[i] + 0.7152 * imgData[i + 1] + 0.0722 * imgData[i + 2]); + + for (var j = 0; j < 4; j++) { + var k = i + j; + + if (Math.abs(imgData[k] - color_from.r) <= sensitivity + && Math.abs(imgData[k + 1] - color_from.g) <= sensitivity + && Math.abs(imgData[k + 2] - color_from.b) <= sensitivity + && Math.abs(imgData[k + 3] - color_from.a) <= sensitivity) { + imgData_tmp[k] = color_to.r; //r + imgData_tmp[k + 1] = color_to.g; //g + imgData_tmp[k + 2] = color_to.b; //b + imgData_tmp[k + 3] = color_to.a; //a + } + } + } + } + + //destination-out + blur = anti-aliasing + ctxTemp.putImageData(img_tmp, 0, 0); + context.globalCompositeOperation = "destination-out"; + if (anti_aliasing == true) { + context.filter = 'blur(1px)'; + } + context.drawImage(canvasTemp, 0, 0); + } + +} +export default Magic_erase_class; diff --git a/frontend/src/js/tools/media.js b/frontend/src/js/tools/media.js new file mode 100644 index 0000000..797c09e --- /dev/null +++ b/frontend/src/js/tools/media.js @@ -0,0 +1,164 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import File_open_class from './../modules/file/open.js'; +import Tools_settings_class from './../modules/tools/settings.js'; +import Dialog_class from './../libs/popup.js'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; + +class Media_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.File_open = new File_open_class(); + this.Tools_settings = new Tools_settings_class(); + this.POP = new Dialog_class(); + this.name = 'media'; + this.cache = []; + this.page = 1; + this.per_page = 50; + } + + load() { + //nothing + } + + render(ctx, layer) { + //nothing + } + + on_activate() { + this.search(); + } + + /** + * Image search api + * + * @param {string} query + * @param {array} data + * @param pages + */ + search(query = '', data = [], pages = null) { + var _this = this; + var html = ''; + var html_paging = ''; + + var key = config.pixabay_key; + key = key.split("").reverse().join(""); + + var safe_search = this.Tools_settings.get_setting('safe_search'); + + if (data.length > 0) { + for (var i in data) { + html += '
'; + html += ' '; + html += '
'; + } + //fix for last line + html += '
'; + html += '
'; + html += '
'; + html += '
'; + + //paging + html_paging += '
'; + html_paging += ' '; + for(var i = 1; i <= Math.min(10, pages); i++) { + var selected = ''; + if(this.page == i){ + var selected = 'selected'; + } + html_paging += ' '; + } + html_paging += ' '; + html_paging += '
'; + } + else{ + this.page = 1; + } + + var settings = { + title: 'Search', + //comment: 'Source: pixabay.com.', + className: 'wide', + params: [ + {name: "query", title: "Keyword:", value: query}, + ], + on_load: function (params, popup) { + var node = document.createElement("div"); + node.classList.add('flex-container'); + node.innerHTML = html + html_paging; + popup.el.querySelector('.dialog_content').appendChild(node); + //events + var targets = popup.el.querySelectorAll('.item img'); + for (var i = 0; i < targets.length; i++) { + targets[i].addEventListener('click', function (event) { + //we have click + var data = { + url: this.dataset.url, + }; + _this.File_open.file_open_url_handler(data); + _this.POP.hide(); + + new app.Actions.Activate_tool_action('select', true).do(); + }); + } + var targets = popup.el.querySelectorAll('#media_paging button'); + for (var i = 0; i < targets.length; i++) { + targets[i].addEventListener('click', function (event) { + //we have click + _this.page = parseInt(this.dataset.value); + _this.POP.save(); + }); + } + }, + on_finish: function (params) { + if (params.query == '') + return; + + var URL = "https://pixabay.com/api/?key=" + key + + "&page=" + _this.page + + "&per_page=" + _this.per_page + + "&safesearch=" + safe_search + + "&q=" + encodeURIComponent(params.query); + + if (_this.cache[URL] != undefined) { + //using cache + + setTimeout(function () { + //only call same function after all handlers finishes + var data = _this.cache[URL]; + + if (parseInt(data.totalHits) == 0) { + alertify.error('Your search did not match any images.'); + } + + var pages = Math.ceil(data.totalHits / _this.per_page); + _this.search(params.query, data.hits, pages); + }, 100); + } + else { + //query to service + $.getJSON(URL, function (data) { + _this.cache[URL] = data; + + if (parseInt(data.totalHits) == 0) { + alertify.error('Your search did not match any images.'); + } + + var pages = Math.ceil(data.totalHits / _this.per_page); + _this.search(params.query, data.hits, pages); + }) + .fail(function () { + alertify.error('Error connecting to service.'); + }); + } + }, + }; + this.POP.show(settings); + + document.getElementById("pop_data_query").select(); + } +} + +export default Media_class; diff --git a/frontend/src/js/tools/pencil.js b/frontend/src/js/tools/pencil.js new file mode 100644 index 0000000..68957c5 --- /dev/null +++ b/frontend/src/js/tools/pencil.js @@ -0,0 +1,306 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; + +class Pencil_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.name = 'pencil'; + this.layer = {}; + this.params_hash = false; + this.pressure_supported = false; + this.pointer_pressure = 0; // has range [0 - 1] + } + + load() { + var _this = this; + + //pointer events + document.addEventListener('pointerdown', function (event) { + _this.pointerdown(event); + }); + document.addEventListener('pointermove', function (event) { + _this.pointermove(event); + }); + + this.default_events(); + } + + dragMove(event) { + if (config.TOOL.name != this.name) + return; + this.mousemove(event); + } + + pointerdown(e) { + // Devices that don't actually support pen pressure can give 0.5 as a false reading. + // It is highly unlikely a real pen will read exactly 0.5 at the start of a stroke. + if (e.pressure && e.pressure !== 0 && e.pressure !== 0.5 && e.pressure <= 1) { + this.pressure_supported = true; + this.pointer_pressure = e.pressure; + } else { + this.pressure_supported = false; + } + } + + pointermove(e) { + // Pressure of exactly 1 seems to be an input error, sometimes I see it when lifting the pen + // off the screen when pressure reading should be near 0. + if (this.pressure_supported && e.pressure < 1) { + this.pointer_pressure = e.pressure; + } + } + + mousedown(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) + return; + + var params_hash = this.get_params_hash(); + var opacity = Math.round(config.ALPHA / 255 * 100); + + if (config.layer.type != this.name || params_hash != this.params_hash) { + //register new object - current layer is not ours or params changed + this.layer = { + type: this.name, + data: [], + opacity: opacity, + params: this.clone(this.getParams()), + status: 'draft', + render_function: [this.name, 'render'], + x: 0, + y: 0, + width: config.WIDTH, + height: config.HEIGHT, + hide_selection_if_active: true, + rotate: null, + is_vector: true, + color: config.COLOR + }; + app.State.do_action( + new app.Actions.Bundle_action('new_pencil_layer', 'New Pencil Layer', [ + new app.Actions.Insert_layer_action(this.layer) + ]) + ); + this.params_hash = params_hash; + } + else { + //continue adding layer data, just register break + const new_data = JSON.parse(JSON.stringify(config.layer.data)); + new_data.push(null); + app.State.do_action( + new app.Actions.Bundle_action('update_pencil_layer', 'Update Pencil Layer', [ + new app.Actions.Update_layer_action(config.layer.id, { + data: new_data + }) + ]) + ); + } + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + + //detect line size + var size = params.size; + var new_size = size; + + if (params.pressure == true && this.pressure_supported) { + new_size = size * this.pointer_pressure * 2; + } + + //more data + config.layer.data.push([ + Math.ceil(mouse.x - config.layer.x), + Math.ceil(mouse.y - config.layer.y), + new_size + ]); + this.Base_layers.render(); + } + + mouseup(e) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.click_valid == false) { + config.layer.status = null; + return; + } + + //detect line size + var size = params.size; + var new_size = size; + + if (params.pressure == true && this.pressure_supported) { + new_size = size * this.pointer_pressure * 2; + } + + //more data + config.layer.data.push([ + Math.ceil(mouse.x - config.layer.x), + Math.ceil(mouse.y - config.layer.y), + new_size + ]); + + this.check_dimensions(); + + config.layer.status = null; + this.Base_layers.render(); + } + + render(ctx, layer) { + this.render_aliased(ctx, layer); + } + + /** + * draw without antialiasing, sharp, ugly mode. + * + * @param {object} ctx + * @param {object} layer + */ + render_aliased(ctx, layer) { + if (layer.data.length == 0) + return; + + var params = layer.params; + var data = layer.data; + var n = data.length; + var size = params.size; + + //set styles + ctx.fillStyle = layer.color; + ctx.strokeStyle = layer.color; + ctx.translate(layer.x, layer.y); + + //draw + ctx.beginPath(); + ctx.moveTo(data[0][0], data[0][1]); + for (var i = 1; i < n; i++) { + if (data[i] === null) { + //break + ctx.beginPath(); + } + else { + //line + size = data[i][2]; + if(size == undefined){ + size = 1; + } + + if (data[i - 1] == null) { + //exception - point + ctx.fillRect( + data[i][0] - Math.floor(size / 2) - 1, + data[i][1] - Math.floor(size / 2) - 1, + size, + size + ); + } + else { + //lines + ctx.beginPath(); + this.draw_simple_line( + ctx, + data[i - 1][0], + data[i - 1][1], + data[i][0], + data[i][1], + size + ); + } + } + } + if (n == 1 || data[1] == null) { + //point + ctx.beginPath(); + ctx.fillRect( + data[0][0] - Math.floor(size / 2) - 1, + data[0][1] - Math.floor(size / 2) - 1, + size, + size + ); + } + + ctx.translate(-layer.x, -layer.y); + } + + /** + * draws line without aliasing + * + * @param {object} ctx + * @param {int} from_x + * @param {int} from_y + * @param {int} to_x + * @param {int} to_y + * @param {int} size + */ + draw_simple_line(ctx, from_x, from_y, to_x, to_y, size) { + var dist_x = from_x - to_x; + var dist_y = from_y - to_y; + var distance = Math.sqrt((dist_x * dist_x) + (dist_y * dist_y)); + var radiance = Math.atan2(dist_y, dist_x); + + for (var j = 0; j < distance; j++) { + var x_tmp = Math.round(to_x + Math.cos(radiance) * j) - Math.floor(size / 2) - 1; + var y_tmp = Math.round(to_y + Math.sin(radiance) * j) - Math.floor(size / 2) - 1; + + ctx.fillRect(x_tmp, y_tmp, size, size); + } + } + + /** + * recalculate layer x, y, width and height values. + */ + check_dimensions() { + if(config.layer.data.length == 0) + return; + + //find bounds + var data = JSON.parse(JSON.stringify(config.layer.data)); // Deep copy for history + var min_x = data[0][0]; + var min_y = data[0][1]; + var max_x = data[0][0]; + var max_y = data[0][1]; + for(var i in data){ + if(data[i] === null) + continue; + min_x = Math.min(min_x, data[i][0]); + min_y = Math.min(min_y, data[i][1]); + max_x = Math.max(max_x, data[i][0]); + max_y = Math.max(max_y, data[i][1]); + } + + //move current data + for(var i in data){ + if(data[i] === null) + continue; + data[i][0] = data[i][0] - min_x; + data[i][1] = data[i][1] - min_y; + } + + //change layers bounds + app.State.do_action( + new app.Actions.Update_layer_action(config.layer.id, { + x: config.layer.x + min_x, + y: config.layer.y + min_y, + width: max_x - min_x, + height: max_y - min_y, + data + }), + { + merge_with_history: ['new_pencil_layer', 'update_pencil_layer'] + } + ); + } + +} + +export default Pencil_class; diff --git a/frontend/src/js/tools/pick_color.js b/frontend/src/js/tools/pick_color.js new file mode 100644 index 0000000..ce92efb --- /dev/null +++ b/frontend/src/js/tools/pick_color.js @@ -0,0 +1,111 @@ +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import Helper_class from './../libs/helpers.js'; +import Base_gui_class from './../core/base-gui.js'; + +class Pick_color_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.Base_gui = new Base_gui_class(); + this.ctx = ctx; + this.name = 'pick_color'; + } + + dragStart(event) { + var _this = this; + if (config.TOOL.name != _this.name) + return; + _this.mousedown(event); + } + + dragMove(event) { + var _this = this; + if (config.TOOL.name != _this.name) + return; + _this.mousemove(event); + } + + load() { + var _this = this; + + //mouse events + document.addEventListener('mousedown', function (event) { + _this.dragStart(event); + }); + document.addEventListener('mousemove', function (event) { + _this.dragMove(event); + }); + document.addEventListener('mouseup', function (event) { + var mouse = _this.get_mouse_info(event); + if (config.TOOL.name != _this.name || mouse.click_valid == false) + return; + _this.copy_color_to_clipboard(); + }); + + // collect touch events + document.addEventListener('touchstart', function (event) { + _this.dragStart(event); + }); + document.addEventListener('touchmove', function (event) { + _this.dragMove(event); + }); + } + + mousedown(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) { + return; + } + + this.pick_color(mouse); + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + if (mouse.is_drag == false || mouse.click_valid == false) { + return; + } + + this.pick_color(mouse); + } + + pick_color(mouse) { + var params = this.getParams(); + + //get canvas from layer + if (params.global == false) { + //active layer + var canvas = this.Base_layers.convert_layer_to_canvas(config.layer.id, null, false); + var ctx = canvas.getContext("2d"); + } + else { + //global + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext("2d"); + canvas.width = config.WIDTH; + canvas.height = config.HEIGHT; + this.Base_layers.convert_layers_to_canvas(ctx, null, false); + } + //find color + var c = ctx.getImageData(mouse.x, mouse.y, 1, 1).data; + var hex = this.Helper.rgbToHex(c[0], c[1], c[2]); + + const newColorDefinition = { hex }; + if (c[3] > 0) { + //set alpha + newColorDefinition.a = c[3]; + } + this.Base_gui.GUI_colors.set_color(newColorDefinition); + } + + copy_color_to_clipboard() { + navigator.clipboard.writeText(config.COLOR); + } + +} + +export default Pick_color_class; diff --git a/frontend/src/js/tools/select.js b/frontend/src/js/tools/select.js new file mode 100644 index 0000000..4adee00 --- /dev/null +++ b/frontend/src/js/tools/select.js @@ -0,0 +1,577 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import Base_selection_class from './../core/base-selection.js'; +import Helper_class from './../libs/helpers.js'; +import Dialog_class from './../libs/popup.js'; + +class Select_tool_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.POP = new Dialog_class(); + this.Helper = new Helper_class(); + this.ctx = ctx; + this.name = 'select'; + this.saved = false; + this.mousedown_dimensions = { x: null, y: null, width: null, height: null }; + this.keyboard_move_start_position = null; + this.moving = false; + this.resizing = false; + this.snap_line_info = {x: null, y: null}; + this.rotate_initial = null; + + var sel_config = { + enable_background: false, + enable_borders: true, + enable_controls: true, + keep_ratio: true, + enable_rotation: true, + enable_move: true, + data_function: function () { + return config.layer; + }, + }; + this.Base_selection = new Base_selection_class(ctx, sel_config, this.name); + } + + load() { + var _this = this; + + //mouse events + document.addEventListener('mousedown', function (e) { + _this.dragStart(e); + }); + document.addEventListener('mousemove', function (e) { + _this.dragMove(e); + }); + document.addEventListener('mouseup', function (e) { + _this.dragEnd(e); + }); + + // collect touch events + document.addEventListener('touchstart', function (e) { + _this.dragStart(e); + }); + document.addEventListener('touchmove', function (e) { + _this.dragMove(e); + }); + document.addEventListener('touchend', function (e) { + _this.dragEnd(e); + }); + + //keyboard actions + document.addEventListener('keydown', (event) => { + if (config.TOOL.name != this.name) + return; + if (this.POP.get_active_instances() > 0) { + return; + } + if (this.Helper.is_input(event.target)) + return; + var k = event.key; + + if (k == "ArrowUp") { + this.move(0, -1, event); + } + else if (k == "ArrowDown") { + this.move(0, 1, event); + } + else if (k == "ArrowRight") { + this.move(1, 0, event); + } + else if (k == "ArrowLeft") { + this.move(-1, 0, event); + } + if (k == "Delete") { + if (config.TOOL.name == this.name) { + app.State.do_action( + new app.Actions.Delete_layer_action(config.layer.id) + ); + } + } + }); + document.addEventListener('keyup', (event) => { + if (config.TOOL.name != this.name) + return; + if (this.POP.active == true) + return; + if (this.Helper.is_input(event.target)) + return; + var k = event.key; + if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(k)) { + if (this.keyboard_move_start_position) { + let x = config.layer.x; + let y = config.layer.y; + config.layer.x = this.keyboard_move_start_position.x; + config.layer.y = this.keyboard_move_start_position.y; + app.State.do_action( + new app.Actions.Update_layer_action(config.layer.id, { x, y }) + ); + this.keyboard_move_start_position = null; + } + } + }); + } + + dragStart(event) { + var mouse = this.get_mouse_info(event); + if (config.TOOL.name != this.name) + return; + if (mouse.click_valid == false) { + return; + } + + this.mousedown(event); + } + + dragMove(event) { + var mouse = this.get_mouse_info(event); + if (config.TOOL.name != this.name) + return; + if (mouse.click_valid == false) { + return; + } + + this.mousemove(event); + } + + dragEnd(event) { + var mouse = this.get_mouse_info(event); + if (config.TOOL.name != this.name) + return; + if (mouse.click_valid == false) { + return; + } + + this.mouseup(event); + this.Base_layers.render(); + } + + async mousedown(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false || config.mouse_lock === true) { + return; + } + + this.rotate_initial = config.layer.rotate; + + if (this.Base_selection.mouse_lock != null) { + this.resizing = true; + this.Base_selection.find_settings().keep_ratio = config.layer.type === 'image'; + if (config.layer.type === 'text' && config.layer.params && config.layer.params.boundary === 'dynamic') { + config.layer.params.boundary = 'box'; + } + } + else { + this.moving = true; + await this.auto_select_object(e); + this.Base_selection.find_settings().keep_ratio = config.layer.type === 'image'; + this.saved = false; + } + + this.mousedown_dimensions = { + x: Math.round(config.layer.x), + y: Math.round(config.layer.y), + width: Math.round(config.layer.width), + height: Math.round(config.layer.height) + }; + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + if (mouse.is_drag == false || mouse.click_valid == false || config.mouse_lock === true) { + return; + } + if (this.resizing) { + + //also handle rotation + let rotate = this.Base_selection.current_angle + if(config.layer.rotate != rotate && rotate !== null){ + config.layer.rotate = rotate; + } + + return; + } + else if (this.moving) { + //move object + config.layer.x = Math.round(mouse.x - mouse.click_x + this.mousedown_dimensions.x); + config.layer.y = Math.round(mouse.y - mouse.click_y + this.mousedown_dimensions.y); + + //apply snap + var snap_info = this.calc_snap(e, config.layer.x, config.layer.y); + if(snap_info != null){ + if(snap_info.x != null) { + config.layer.x = snap_info.x; + } + if(snap_info.y != null) { + config.layer.y = snap_info.y; + } + } + + config.need_render = true; + } + } + + mouseup(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false || config.mouse_lock === true) { + return; + } + if (this.resizing) { + let x = config.layer.x; + let y = config.layer.y; + let width = config.layer.width; + let height = config.layer.height; + + //reset values + config.layer.x = this.mousedown_dimensions.x; + config.layer.y = this.mousedown_dimensions.y; + config.layer.width = this.mousedown_dimensions.width; + config.layer.height = this.mousedown_dimensions.height; + if ( + this.mousedown_dimensions.x !== x || this.mousedown_dimensions.y !== y || + this.mousedown_dimensions.width !== width || this.mousedown_dimensions.height !== height + ) { + app.State.do_action( + new app.Actions.Bundle_action('resize_layer', 'Resize Layer', [ + new app.Actions.Update_layer_action(config.layer.id, { + x, y, width, height + }) + ]) + ); + } + + //also handle rotation + let rotate = this.Base_selection.current_angle; + if(this.rotate_initial != rotate && rotate !== null){ + //save state + config.layer.rotate = this.rotate_initial; + app.State.do_action( + new app.Actions.Bundle_action('resize_layer', 'Resize Layer', [ + new app.Actions.Update_layer_action(config.layer.id, { + rotate + }) + ]) + ); + } + } + else if (this.moving) { + var new_x = Math.round(mouse.x - mouse.click_x + this.mousedown_dimensions.x); + var new_y = Math.round(mouse.y - mouse.click_y + this.mousedown_dimensions.y); + config.layer.x = this.mousedown_dimensions.x; + config.layer.y = this.mousedown_dimensions.y; + + if(mouse.x - mouse.click_x || mouse.y - mouse.click_y) { + var snap_info = this.calc_snap(e, new_x, new_y); + if (snap_info != null) { + if (snap_info.x != null) { + new_x = snap_info.x; + } + if (snap_info.y != null) { + new_y = snap_info.y; + } + } + } + + if (this.mousedown_dimensions.x !== new_x || this.mousedown_dimensions.y !== new_y) { + app.State.do_action( + new app.Actions.Bundle_action('move_layer', 'Move Layer', [ + new app.Actions.Update_layer_action(config.layer.id, { + x: new_x, + y: new_y + }) + ]) + ); + } + } + this.moving = false; + this.resizing = false; + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + var mouse = this.get_mouse_info(event); + + //maybe related tool have additional overlay render handlers? + if(config.layer.render_function != null) { + var render_class = config.layer.render_function[0]; + var render_function = 'select'; + if ( + typeof this.Base_gui.GUI_tools.tools_modules[render_class].object[ + render_function + ] != "undefined" + ) { + this.Base_gui.GUI_tools.tools_modules[render_class].object[ + render_function + ](this.ctx); + } + } + + if (mouse.is_drag == false) + return; + + this.render_overlay_parent(ctx); + } + + /** + * calculates current object snap coordinates and returns it. One of coordinates can be null. + * + * @param event + * @param pos_x + * @param pos_y + * @returns object|null + */ + calc_snap(event, pos_x, pos_y) { + var snap_position = { x: null, y: null }; + var params = this.getParams(); + + if(config.SNAP === false || event.shiftKey == true){ + this.snap_line_info = {x: null, y: null}; + return null; + } + + //settings + var sensitivity = 0.01; + var max_distance = (config.WIDTH + config.HEIGHT) / 2 * sensitivity / config.ZOOM; + + //collect snap positions + var snap_positions = this.get_snap_positions(config.layer.id); + + //find closest snap positions + var min_group = { + x: { + start: null, + center: null, + end: null, + }, + y: { + start: null, + center: null, + end: null, + }, + }; + var min_group_distance = { + x: { + start: null, + center: null, + end: null, + }, + y: { + start: null, + center: null, + end: null, + }, + }; + //x + for(var i in snap_positions.x){ + var distance = Math.abs(pos_x - snap_positions.x[i]); + if(distance < max_distance && (distance < min_group_distance.x.start || min_group_distance.x.start === null)){ + min_group_distance.x.start = distance; + min_group.x.start = snap_positions.x[i]; + } + + var distance = Math.abs(pos_x + config.layer.width/2 - snap_positions.x[i]); + if(distance < max_distance && (distance < min_group_distance.x.center || min_group_distance.x.center === null)){ + min_group_distance.x.center = distance; + min_group.x.center = snap_positions.x[i]; + } + + var distance = Math.abs(pos_x + config.layer.width - snap_positions.x[i]); + if(distance < max_distance && (distance < min_group_distance.x.end || min_group_distance.x.end === null)){ + min_group_distance.x.end = distance; + min_group.x.end = snap_positions.x[i]; + } + } + //y + for(var i in snap_positions.y){ + var distance = Math.abs(pos_y - snap_positions.y[i]); + if(distance < max_distance && (distance < min_group_distance.y.start || min_group_distance.y.start === null)){ + min_group_distance.y.start = distance; + min_group.y.start = snap_positions.y[i]; + } + + var distance = Math.abs(pos_y + config.layer.height/2 - snap_positions.y[i]); + if(distance < max_distance && (distance < min_group_distance.y.center || min_group_distance.y.center === null)){ + min_group_distance.y.center = distance; + min_group.y.center = snap_positions.y[i]; + } + + var distance = Math.abs(pos_y + config.layer.height - snap_positions.y[i]); + if(distance < max_distance && (distance < min_group_distance.y.end || min_group_distance.y.end === null)){ + min_group_distance.y.end = distance; + min_group.y.end = snap_positions.y[i]; + } + } + + //find best begin, center, end + var min_distance = { + x: null, + y: null, + }; + //x + if(min_group_distance.x.start != null) + min_distance.x = min_group_distance.x.start; + if(min_group_distance.x.center != null && (min_group_distance.x.center < min_distance.x || min_distance.x === null)) + min_distance.x = min_group_distance.x.center; + if(min_group_distance.x.end != null && (min_group_distance.x.end < min_distance.x || min_distance.x === null)) + min_distance.x = min_group_distance.x.end; + //y + if(min_group_distance.y.start != null) + min_distance.y = min_group_distance.y.start; + if(min_group_distance.y.center != null && (min_group_distance.y.center < min_distance.y || min_distance.y === null)) + min_distance.y = min_group_distance.y.center; + if(min_group_distance.y.end != null && (min_group_distance.y.end < min_distance.y || min_distance.y === null)) + min_distance.y = min_group_distance.y.end; + + //apply snap + var success = false; + //x + if(min_group.x.center != null && min_group_distance.x.center == min_distance.x) { + snap_position.x = Math.round(min_group.x.center - config.layer.width / 2); + success = true; + this.snap_line_info.x = { + start_x: min_group.x.center, + start_y: 0, + end_x: min_group.x.center, + end_y: config.HEIGHT + }; + } + else if(min_group.x.start != null && min_group_distance.x.start == min_distance.x) { + snap_position.x = Math.round(min_group.x.start); + success = true; + this.snap_line_info.x = { + start_x: min_group.x.start, + start_y: 0, + end_x: min_group.x.start, + end_y: config.HEIGHT, + }; + } + else if(min_group.x.end != null && min_group_distance.x.end == min_distance.x) { + snap_position.x = Math.round(min_group.x.end - config.layer.width); + success = true; + this.snap_line_info.x = { + start_x: min_group.x.end, + start_y: 0, + end_x: min_group.x.end, + end_y: config.HEIGHT + }; + } + else{ + this.snap_line_info.x = null; + } + //y + if(min_group.y.center != null && min_group_distance.y.center == min_distance.y) { + snap_position.y = Math.round(min_group.y.center - config.layer.height / 2); + success = true; + this.snap_line_info.y = { + start_x: 0, + start_y: min_group.y.center, + end_x: config.WIDTH, + end_y: min_group.y.center, + }; + } + else if(min_group.y.start != null && min_group_distance.y.start == min_distance.y) { + snap_position.y = Math.round(min_group.y.start); + success = true; + this.snap_line_info.y = { + start_x: 0, + start_y: min_group.y.start, + end_x: config.WIDTH, + end_y: min_group.y.start, + }; + } + else if(min_group.y.end != null && min_group_distance.y.end == min_distance.y) { + snap_position.y = Math.round(min_group.y.end - config.layer.height); + success = true; + this.snap_line_info.y = { + start_x: 0, + start_y: min_group.y.end, + end_x: config.WIDTH, + end_y: min_group.y.end, + }; + } + else{ + this.snap_line_info.y = null; + } + + if(success) { + return snap_position; + } + + return null; + } + + move(direction_x, direction_y, event) { + if (!this.keyboard_move_start_position) { + this.keyboard_move_start_position = { + x: config.layer.x, + y: config.layer.y + } + } + var power = 10; + if (event.ctrlKey == true || event.metaKey) + power = 50; + if (event.shiftKey == true) + power = 1; + + config.layer.x += direction_x * power; + config.layer.y += direction_y * power; + config.need_render = true; + } + + async auto_select_object(e) { + var params = this.getParams(); + if (params.auto_select == false) + return; + + var layers_sorted = this.Base_layers.get_sorted_layers(); + + //render main canvas + for (var i = 0; i < layers_sorted.length; i++) { + var value = layers_sorted[i]; + var canvas = this.Base_layers.convert_layer_to_canvas(value.id, null, false); + + if (this.check_hit_region(e, canvas.getContext("2d"), value) == true) { + await app.State.do_action( + new app.Actions.Select_layer_action(value.id) + ); + break; + } + } + } + + check_hit_region(e, ctx, layer) { + var mouse = this.get_mouse_info(e); + + if(layer.type == 'image' && Math.abs(layer.width * layer.height / 1000000) > 5){ + //too big to check using getImageData - use simple way + if (mouse.x > layer.x && mouse.x < layer.x + layer.width && + mouse.y > layer.y && mouse.y < layer.y + layer.height) { + //hit + return true; + } + + return false; + } + + var data = ctx.getImageData(mouse.x, mouse.y, 1, 1).data; + var blank = [0, 0, 0, 0]; + if (config.TRANSPARENCY == false) { + blank = [0, 0, 0, 0]; + } + + if (data[0] != blank[0] || data[1] != blank[1] || data[2] != blank[2] + || data[3] != blank[3]) { + //hit + return true; + } + + return false; + } + +} + +export default Select_tool_class; diff --git a/frontend/src/js/tools/selection.js b/frontend/src/js/tools/selection.js new file mode 100644 index 0000000..395b7c7 --- /dev/null +++ b/frontend/src/js/tools/selection.js @@ -0,0 +1,348 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import Base_selection_class from './../core/base-selection.js'; +import GUI_tools_class from './../core/gui/gui-tools.js'; +import Helper_class from './../libs/helpers.js'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; + +var instance = null; + +class Selection_class extends Base_tools_class { + + constructor(ctx) { + super(); + + //singleton + if (instance) { + return instance; + } + instance = this; + + var _this = this; + + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.ctx = ctx; + this.name = 'selection'; + this.type = null; + this.tmpCanvas = null; + this.tmpCanvasCtx = null; + this.selection_coords_from = null; + this.selection = { + x: null, + y: null, + width: null, + height: null, + }; + + var sel_config = { + enable_background: true, + enable_borders: true, + enable_controls: false, + enable_rotation: false, + enable_move: false, + data_function: function () { + return _this.selection; + }, + }; + this.mousedown_selection = null; + this.Base_selection = new Base_selection_class(ctx, sel_config, this.name); + this.GUI_tools = new GUI_tools_class(); + } + + load() { + var _this = this; + + //mouse events + document.addEventListener('mousedown', function (event) { + _this.dragStart(event); + }); + document.addEventListener('mousemove', function (event) { + _this.dragMove(event); + }); + document.addEventListener('mouseup', function (event) { + _this.dragEnd(event); + }); + + // collect touch events + document.addEventListener('touchstart', function (event) { + _this.dragStart(event); + }); + document.addEventListener('touchmove', function (event) { + _this.dragMove(event); + }); + document.addEventListener('touchend', function (event) { + _this.dragEnd(event); + }); + + document.addEventListener('keydown', (e) => { + var code = e.keyCode; + if (this.Helper.is_input(e.target)) + return; + + if (code == 27) { + //escape + app.State.do_action(new app.Actions.Bundle_action('clear_selection', 'Clear Selection', this.on_leave())); + } + if (code == 46) { + //delete + if (config.TOOL.name == this.name) { + this.delete_selection(); + } + } + if (code == 65 && (e.ctrlKey == true || e.metaKey)) { + //A + e.preventDefault(); + this.select_all(); + } + }, false); + } + + dragStart(event) { + var _this = this; + if (config.TOOL.name != _this.name) + return; + _this.mousedown(event); + } + + dragMove(event) { + var _this = this; + if (config.TOOL.name != _this.name) + return; + _this.mousemove(event); + } + + dragEnd(event) { + var _this = this; + if (config.TOOL.name != _this.name) + return; + _this.mouseup(event); + } + + mousedown(e) { + var mouse = this.get_mouse_info(e); + var layer = config.layer; + if (this.Base_selection.is_drag == false || mouse.click_valid == false) + return; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + this.mousedown_selection = JSON.parse(JSON.stringify(this.selection)); + + if (this.selection.width != null && this.selection.height != null + && mouse.x > this.selection.x + && mouse.x < this.selection.x + this.selection.width + && mouse.y > this.selection.y + && mouse.y < this.selection.y + this.selection.height + && layer.width == layer.width_original && layer.height == layer.height_original + ) { + //move + this.type = 'move'; + + if (this.tmpCanvas == null) { + this.init_tmp_canvas(); + + //register tmp canvas for faster redraw + config.layer.link_canvas = this.tmpCanvas; + config.need_render = true; + } + } + else { + //create new selection + this.selection = { + x: mouse.x, + y: mouse.y, + width: 0, + height: 0, + }; + this.type = 'create'; + this.selection_coords_from = {x: mouse.x, y: mouse.y}; + } + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + if (this.Base_selection.is_drag == false || mouse.is_drag == false) + return; + if (e.type == 'mousedown' && (mouse.click_valid == false) || config.layer.type != 'image') { + return; + } + if (this.selection_coords_from === null) { + return; + } + if (this.type == 'create') { + //create new selection + this.selection.width = mouse.x - mouse.click_x; + this.selection.height = mouse.y - mouse.click_y; + config.need_render = true; + } + } + + mouseup(e) { + var mouse = this.get_mouse_info(e); + + if (!this.Base_selection.is_drag) { + return; + } + if ((e.type == 'mousedown' && mouse.click_valid == false) || config.layer.type != 'image') { + return; + } + if (this.type === 'move') { + return; // Translate appears to not work at the moment + } + + var width = mouse.x - this.selection.x; + var height = mouse.y - this.selection.y; + + if (width == 0 || height == 0) { + //cancel selection + app.State.do_action( + new app.Actions.Bundle_action('clear_selection', 'Clear Selection', this.on_leave()) + ); + return; + } + + if (this.selection.width != null && this.selection.height != null) { + //make sure coords not negative + var details = this.selection; + var x = details.x; + var y = details.y; + if (details.width < 0) { + x = x + details.width; + this.selection_coords_from.x = x; + } + if (details.height < 0) { + y = y + details.height; + this.selection_coords_from.y = y; + } + this.selection = { + x: x, + y: y, + width: Math.abs(details.width), + height: Math.abs(details.height), + }; + app.State.do_action( + new app.Actions.Set_selection_action(this.selection.x, this.selection.y, this.selection.width, this.selection.height, this.mousedown_selection) + ); + } + } + + select_all() { + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + let actions = []; + + if (config.TOOL.name != this.name) { + actions.push( + new app.Actions.Activate_tool_action(this.name) + ); + } + actions.push( + new app.Actions.Set_selection_action(0, 0, config.WIDTH, config.HEIGHT, this.selection) + ); + app.State.do_action( + new app.Actions.Bundle_action('select_all', 'Select All', actions) + ); + } + + render(ctx, layer) { + //nothing + } + + save_translate() { + if (this.tmpCanvas == null) + return; + + delete config.layer.link_canvas; + app.State.do_action( + new app.Actions.Bundle_action('selection_tool', 'Selection Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); + + this.reset_tmp_canvas(); + config.need_render = true; + } + + delete_selection() { + var selection = this.selection; + var layer = config.layer; + + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + + if (selection == null) { + alertify.error('Nothing is selected.'); + return; + } + + this.init_tmp_canvas(); + + var mouse_x = selection.x - layer.x; + var mouse_y = selection.y - layer.y; + + //adapt to origin size + mouse_x = this.adaptSize(mouse_x, 'width'); + mouse_y = this.adaptSize(mouse_y, 'height'); + selection.width = this.adaptSize(selection.width, 'width'); + selection.height = this.adaptSize(selection.height, 'height'); + + //do erase + this.tmpCanvasCtx.clearRect(mouse_x, mouse_y, selection.width, selection.height); + + app.State.do_action( + new app.Actions.Bundle_action('delete_selection', 'Delete Selection', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas), + new app.Actions.Reset_selection_action(this.selection) + ]) + ); + + this.reset_tmp_canvas(); + delete config.layer.link_canvas; + this.reset_tmp_canvas(); + } + + init_tmp_canvas() { + this.tmpCanvas = document.createElement('canvas'); + this.tmpCanvasCtx = this.tmpCanvas.getContext("2d"); + this.tmpCanvas.width = config.layer.width_original; + this.tmpCanvas.height = config.layer.height_original; + this.tmpCanvasCtx.drawImage(config.layer.link, 0, 0); + } + + on_leave() { + let actions = [ + new app.Actions.Reset_selection_action(this.selection) + ]; + delete config.layer.link_canvas; + this.reset_tmp_canvas(); + return actions; + } + + clear_selection() { + app.State.do_action( + new app.Actions.Bundle_action('clear_selection', 'Clear Selection', this.on_leave()) + ); + } + + reset_tmp_canvas() { + if (this.tmpCanvas == null) + return; + this.tmpCanvas.width = 1; + this.tmpCanvas.height = 1; + this.tmpCanvas = null; + this.tmpCanvasCtx = null; + } + +} +; +export default Selection_class; diff --git a/frontend/src/js/tools/shape.js b/frontend/src/js/tools/shape.js new file mode 100644 index 0000000..2269df3 --- /dev/null +++ b/frontend/src/js/tools/shape.js @@ -0,0 +1,137 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import Dialog_class from './../libs/popup.js'; +import GUI_tools_class from './../core/gui/gui-tools.js'; + +var instance = null; + +class Shape_class extends Base_tools_class { + + constructor(ctx) { + super(); + + //singleton + if (instance) { + return instance; + } + instance = this; + + this.Base_layers = new Base_layers_class(); + this.GUI_tools = new GUI_tools_class(); + this.POP = new Dialog_class(); + this.ctx = ctx; + this.name = 'shape'; + this.layer = {}; + this.preview_width = 150; + this.preview_height = 120; + + this.set_events(); + } + + set_events() { + document.addEventListener('keydown', (event) => { + var code = event.keyCode; + if (this.Helper.is_input(event.target)) + return; + + if (code == 72) { + //H + this.show_shapes(); + } + }, false); + } + + load() { + + } + + on_activate() { + this.show_shapes(); + } + + async show_shapes(){ + var _this = this; + var html = ''; + + var data = this.get_shapes(); + + for (var i in data) { + html += '
'; + html += ' '; + html += '
' + data[i].title + '
'; + html += '
'; + } + for (var i = 0; i < 4; i++) { + html += '
'; + } + + var settings = { + title: 'Shapes', + className: 'wide', + on_load: function (params, popup) { + var node = document.createElement("div"); + node.classList.add('flex-container'); + node.innerHTML = html; + popup.el.querySelector('.dialog_content').appendChild(node); + //events + var targets = popup.el.querySelectorAll('.item canvas'); + for (var i = 0; i < targets.length; i++) { + targets[i].addEventListener('click', function (event) { + //we have click + _this.GUI_tools.activate_tool(this.dataset.key); + _this.POP.hide(); + }); + } + }, + }; + this.POP.show(settings); + + //sleep, lets wait till DOM is finished + await new Promise(r => setTimeout(r, 10)); + + //draw demo thumbs + for (var i in data) { + var function_name = 'demo'; + var canvas = document.getElementById('c_'+data[i].key); + var ctx = canvas.getContext("2d"); + + if(typeof data[i].object[function_name] == "undefined") + continue; + + data[i].object[function_name](ctx, 20, 20, this.preview_width - 40, this.preview_height - 40, null); + } + } + + render(ctx, layer) { + + } + + get_shapes(){ + var list = []; + + for (var i in this.Base_gui.GUI_tools.tools_modules) { + var object = this.Base_gui.GUI_tools.tools_modules[i]; + if (object.full_key.indexOf("shapes/") == -1 ) + continue; + + list.push(object); + } + + list.sort(function(a, b) { + var nameA = a.title.toUpperCase(); + var nameB = b.title.toUpperCase(); + if (nameA < nameB) return -1; + if (nameA > nameB) return 1; + return 0; + }); + + return list; + } + +} + +export default Shape_class; diff --git a/frontend/src/js/tools/shapes/arrow.js b/frontend/src/js/tools/shapes/arrow.js new file mode 100644 index 0000000..7167627 --- /dev/null +++ b/frontend/src/js/tools/shapes/arrow.js @@ -0,0 +1,212 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Arrow_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'arrow'; + this.layer = {}; + this.best_ratio = 1; + this.snap_line_info = {x: null, y: null}; + this.mouse_click = {x: null, y: null}; + } + + load() { + this.default_events(); + } + + mousedown(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) { + return; + } + + var mouse_x = mouse.x; + var mouse_y = mouse.y; + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + + this.mouse_click.x = mouse_x; + this.mouse_click.y = mouse_y; + + //register new object - current layer is not ours or params changed + this.layer = { + type: this.name, + params: this.clone(this.getParams()), + status: 'draft', + render_function: [this.name, 'render'], + x: Math.round(mouse_x), + y: Math.round(mouse_y), + rotate: null, + is_vector: true, + color: config.COLOR + }; + app.State.do_action( + new app.Actions.Bundle_action('new_line_layer', 'New Line Layer', [ + new app.Actions.Insert_layer_action(this.layer) + ]) + ); + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + + var mouse_x = Math.round(mouse.x); + var mouse_y = Math.round(mouse.y); + var click_x = Math.round(this.mouse_click.x); + var click_y = Math.round(this.mouse_click.y); + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y, config.layer.id); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + + var width = mouse_x - this.layer.x; + var height = mouse_y - this.layer.y; + if (e.ctrlKey == true || e.metaKey) { + //one direction only + if (Math.abs(width) < Math.abs(height)) + width = 0; + else + height = 0; + } + + //more data + config.layer.width = width; + config.layer.height = height; + + this.Base_layers.render(); + } + + mouseup(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) { + config.layer.status = null; + return; + } + + var mouse_x = Math.round(mouse.x); + var mouse_y = Math.round(mouse.y); + var click_x = Math.round(this.mouse_click.x); + var click_y = Math.round(this.mouse_click.y); + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y, config.layer.id); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + this.snap_line_info = {x: null, y: null}; + + var width = mouse_x - this.layer.x; + var height = mouse_y - this.layer.y; + + if (width == 0 && height == 0) { + //same coordinates - cancel + app.State.scrap_last_action(); + return; + } + + if (e.ctrlKey == true || e.metaKey) { + //one direction only + if (Math.abs(width) < Math.abs(height)) + width = 0; + else + height = 0; + } + + //more data + app.State.do_action( + new app.Actions.Update_layer_action(config.layer.id, { + width, + height, + status: null + }), + { merge_with_history: 'new_line_layer' } + ); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + ctx.fillStyle = '#aaa'; + ctx.strokeStyle = '#555'; + ctx.lineWidth = 2; + + this.arrow(ctx, x, y, x + width, y + height, 15); + } + + render(ctx, layer) { + if (layer.width == 0 && layer.height == 0) + return; + + var params = layer.params; + + //set styles + ctx.fillStyle = layer.color; + ctx.strokeStyle = layer.color; + ctx.lineWidth = params.size; + ctx.lineCap = 'round'; + + var width = layer.x + layer.width; + var height = layer.y + layer.height; + + var headlen = params.size * 7; + if (headlen < 15) + headlen = 15; + this.arrow(ctx, + layer.x, layer.y, + width, height, + headlen); + } + + arrow(ctx, fromx, fromy, tox, toy, headlen) { + var dx = tox - fromx; + var dy = toy - fromy; + var angle = Math.atan2(dy, dx); + ctx.beginPath(); + ctx.moveTo(fromx, fromy); + ctx.lineTo(tox, toy); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(tox - headlen * Math.cos(angle - Math.PI / 6), toy - headlen * Math.sin(angle - Math.PI / 6)); + ctx.lineTo(tox, toy); + ctx.lineTo(tox - headlen * Math.cos(angle + Math.PI / 6), toy - headlen * Math.sin(angle + Math.PI / 6)); + ctx.stroke(); + } + +} + +export default Arrow_class; diff --git a/frontend/src/js/tools/shapes/bezier_curve.js b/frontend/src/js/tools/shapes/bezier_curve.js new file mode 100644 index 0000000..a518073 --- /dev/null +++ b/frontend/src/js/tools/shapes/bezier_curve.js @@ -0,0 +1,482 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Helper_class from './../../libs/helpers.js'; + +class Bezier_Curve_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.ctx = ctx; + this.name = 'bezier_curve'; + this.layer = {}; + this.best_ratio = 1; + this.snap_line_info = {x: null, y: null}; + this.params_hash = false; + this.selected_obj_positions = {}; + this.mouse_lock = null; + this.selected_object_drag_type = null; + this.old_data = null; + + this.events(); + } + + load() { + var _this = this; + this.default_events(); + document.addEventListener('keydown', function (event) { + if (config.TOOL.name != _this.name) { + return; + } + var code = event.code; + if (code == "Escape") { + //escape + } + }); + } + + /** + * events for handling helping lines only + */ + events() { + document.addEventListener('mousedown', (e) => { + this.selected_object_actions(e); + }); + document.addEventListener('mousemove', (e) => { + this.selected_object_actions(e); + }); + document.addEventListener('mouseup', (e) => { + this.selected_object_actions(e); + }); + + // touch + document.addEventListener('touchstart', (event) => { + this.selected_object_actions(event); + }); + document.addEventListener('touchmove', (event) => { + this.selected_object_actions(event); + }, {passive: false}); + document.addEventListener('touchend', (event) => { + this.selected_object_actions(event); + }); + } + + mousedown(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) { + return; + } + + var params_hash = this.get_params_hash(); + + var mouse_x = mouse.x; + var mouse_y = mouse.y; + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + + const data_clone = JSON.parse(JSON.stringify(config.layer.data)); + + if (config.layer.type != this.name || params_hash != this.params_hash + || (data_clone != null && data_clone.cp2.x !== null)) { + //register new object - current layer is not ours or params changed + this.layer = { + type: this.name, + data: { + start: {x: mouse_x, y: mouse_y}, + cp1: {x: null, y: null}, + cp2: {x: null, y: null}, + end: {x: null, y: null} + }, + params: this.clone(this.getParams()), + render_function: [this.name, 'render'], + x: 0, + y: 0, + width: null, + height: null, + hide_selection_if_active: true, + rotate: null, + is_vector: true, + color: config.COLOR, + status: 'draft', + }; + app.State.do_action( + new app.Actions.Bundle_action('new_bezier_layer', 'New Bezier Layer', [ + new app.Actions.Insert_layer_action(this.layer) + ]) + ); + this.params_hash = params_hash; + } + else { + //add more data + config.layer.data.end.x = mouse_x; + config.layer.data.end.y = mouse_y; + } + + this.Base_layers.render(); + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + var mouse_x = Math.round(mouse.x); + var mouse_y = Math.round(mouse.y); + var click_x = Math.round(mouse.click_x); + var click_y = Math.round(mouse.click_y); + + if (mouse.click_valid == false) { + return; + } + if (mouse.is_drag == false) { + return; + } + + if (e.ctrlKey == true || e.metaKey) { + //one direction only + var width = mouse_x - click_x; + var height = mouse_y - click_y; + + if (Math.abs(width) > Math.abs(height)) + mouse_y = click_y; + else + mouse_x = click_x; + } + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y, config.layer.id); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + + //add more data + if(config.layer.data.end.x === null){ + //still first step + config.layer.data.cp1.x = mouse_x; + config.layer.data.cp1.y = mouse_y; + } + else{ + config.layer.data.cp2.x = mouse_x; + config.layer.data.cp2.y = mouse_y; + } + + this.Base_layers.render(); + } + + mouseup(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) { + return; + } + + var mouse_x = Math.round(mouse.x); + var mouse_y = Math.round(mouse.y); + var click_x = Math.round(mouse.click_x); + var click_y = Math.round(mouse.click_y); + + if (e.ctrlKey == true || e.metaKey) { + //one direction only + var width = mouse_x - click_x; + var height = mouse_y - click_y; + + if (Math.abs(width) > Math.abs(height)) + mouse_y = click_y; + else + mouse_x = click_x; + } + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y, config.layer.id); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + this.snap_line_info = {x: null, y: null}; + + //add more data + if(config.layer.data.end.x === null){ + //still first step + config.layer.data.cp1.x = mouse_x; + config.layer.data.cp1.y = mouse_y; + } + else{ + config.layer.data.cp2.x = mouse_x; + config.layer.data.cp2.y = mouse_y; + config.layer.status = null; + } + + this.Base_layers.render(); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + + //also draw control lines + if(config.layer.type == this.name){ + var bezier = config.layer.data; + this.selected_obj_positions = {}; + + var x = config.layer.x; + var y = config.layer.y; + + //draw corners + if (bezier.start.x != null) { + this.Helper.draw_special_line( + this.ctx, + x + bezier.start.x, + y + bezier.start.y, + x + bezier.cp1.x, + y + bezier.cp1.y + ); + if(config.TOOL.name == 'select') { + this.selected_obj_positions.cp1_start = this.Helper.draw_control_point( + this.ctx, + x + bezier.start.x, + y + bezier.start.y + ); + this.selected_obj_positions.cp1_end = this.Helper.draw_control_point( + this.ctx, + x + bezier.cp1.x, + y + bezier.cp1.y + ); + } + } + if (bezier.end.x != null && bezier.cp2.x != null) { + this.Helper.draw_special_line( + this.ctx, + x + bezier.end.x, + y + bezier.end.y, + x + bezier.cp2.x, + y + bezier.cp2.y + ); + if(config.TOOL.name == 'select') { + this.selected_obj_positions.cp2_start = this.Helper.draw_control_point( + this.ctx, + x + bezier.end.x, + y + bezier.end.y + ); + this.selected_obj_positions.cp2_end = this.Helper.draw_control_point( + this.ctx, + x + bezier.cp2.x, + y + bezier.cp2.y + ); + } + } + } + } + + select(ctx) { + this.render_overlay(ctx); + } + + demo(ctx, x, y, width, height) { + var data = { + start: {x: x, y: y}, + cp1: {x: x + width, y: y}, + cp2: {x: x, y: y + height}, + end: {x: x + width, y: y + height} + }; + + this.draw_bezier(ctx, 0, 0, data, 2, '#555'); + } + + render(ctx, layer) { + var params = layer.params; + this.draw_bezier(ctx, layer.x, layer.y, layer.data, params.size, layer.color); + } + + draw_bezier(ctx, x, y, data, lineWidth, color) { + if(data.end.x == null || data.cp2.x == null){ + return; + } + + //set styles + ctx.fillStyle = color; + ctx.strokeStyle = color; + ctx.lineWidth = lineWidth; + ctx.lineCap = 'round'; + + //draw bezier + ctx.beginPath(); + ctx.moveTo(x + data.start.x, y + data.start.y); + ctx.bezierCurveTo( + x + data.cp1.x, y + data.cp1.y, + x + data.cp2.x, y + data.cp2.y, + x + data.end.x, y + data.end.y + ); + ctx.stroke(); + } + + selected_object_actions(e) { + if(config.TOOL.name != 'select' || config.layer.type != this.name || config.layer.status == 'draft'){ + return; + } + + var ctx = this.Base_layers.ctx; + + var mouse = this.get_mouse_info(e); + var mouse_x = Math.round(mouse.x); + var mouse_y = Math.round(mouse.y); + var click_x = Math.round(mouse.click_x); + var click_y = Math.round(mouse.click_y); + + const mainWrapper = document.getElementById('main_wrapper'); + + //simplify checks + var event_type = e.type; + if(event_type == 'touchstart') event_type = 'mousedown'; + if(event_type == 'touchmove') event_type = 'mousemove'; + if(event_type == 'touchend') event_type = 'mouseup'; + + if (event_type == 'mouseup') { + //reset + config.mouse_lock = null; + if (mainWrapper.style.cursor != 'default') { + mainWrapper.style.cursor = 'default'; + } + } + + if (event_type == 'mousedown' && config.mouse.valid == false) { + return; + } + + if (event_type == 'mousemove' && this.mouse_lock == 'move_point' && mouse.is_drag) { + mainWrapper.style.cursor = "move"; + + if (e.buttons == 1 || typeof e.buttons == "undefined") { + var type = this.selected_object_drag_type; + var bezier = config.layer.data; + + // Do transformations + var dx = Math.round(mouse.x - mouse.click_x) - config.layer.x; + var dy = Math.round(mouse.y - mouse.click_y) - config.layer.y; + + // Set values + if(type == 'cp1_start') { + bezier.start.x = mouse.click_x + dx; + bezier.start.y = mouse.click_y + dy; + + if (e.ctrlKey == true || e.metaKey) { + //one direction only + var width = mouse_x - bezier.cp1.x; + var height = mouse_y - bezier.cp1.y; + if (Math.abs(width) > Math.abs(height)) + bezier.start.y = bezier.cp1.y; + else + bezier.start.x = bezier.cp1.x; + } + } + else if(type == 'cp1_end') { + bezier.cp1.x = mouse.click_x + dx; + bezier.cp1.y = mouse.click_y + dy; + + if (e.ctrlKey == true || e.metaKey) { + //one direction only + var width = mouse_x -bezier.start.x; + var height = mouse_y - bezier.start.y; + if (Math.abs(width) > Math.abs(height)) + bezier.cp1.y = bezier.start.y; + else + bezier.cp1.x = bezier.start.x; + } + } + else if(type == 'cp2_start') { + bezier.end.x = mouse.click_x+ dx; + bezier.end.y = mouse.click_y + dy; + + if (e.ctrlKey == true || e.metaKey) { + //one direction only + var width = mouse_x - bezier.cp2.x; + var height = mouse_y - bezier.cp2.y; + if (Math.abs(width) > Math.abs(height)) + bezier.end.y = bezier.cp2.y; + else + bezier.end.x = bezier.cp2.x; + } + } + else if(type == 'cp2_end') { + bezier.cp2.x = mouse.click_x + dx; + bezier.cp2.y = mouse.click_y + dy; + + if (e.ctrlKey == true || e.metaKey) { + //one direction only + var width = mouse_x - bezier.end.x; + var height = mouse_y - bezier.end.y; + if (Math.abs(width) > Math.abs(height)) + bezier.cp2.y = bezier.end.y; + else + bezier.cp2.x = bezier.end.x; + } + } + + config.need_render = true; + } + return; + } + if (event_type == 'mouseup' && this.mouse_lock == 'move_point') { + this.mouse_lock = null; + var type = this.selected_object_drag_type; + var bezier = config.layer.data; + + //reset sate + config.layer.data = this.old_data; + + //save state + app.State.do_action( + new app.Actions.Bundle_action('change_layer_details', 'Change Layer Details', [ + new app.Actions.Update_layer_action(config.layer.id, { + data: bezier, + }) + ]) + ); + + config.need_render = true; + } + + if (!mouse.is_drag && ['mousedown', 'mouseup'].includes(event_type)) { + return; + } + + if (!this.mouse_lock) { + for (let current_drag_type in this.selected_obj_positions) { + const position = this.selected_obj_positions[current_drag_type]; + if (position && this.ctx.isPointInPath(position, mouse.x, mouse.y)) { + // match + if (event_type == 'mousedown') { + if (e.buttons == 1 || typeof e.buttons == "undefined") { + this.mouse_lock = 'move_point'; + this.selected_object_drag_type = current_drag_type; + } + config.mouse_lock = true; + this.old_data = JSON.parse(JSON.stringify(config.layer.data)); + } + if (event_type == 'mousemove') { + mainWrapper.style.cursor = 'move'; + } + } + } + } + } + +} + +export default Bezier_Curve_class; diff --git a/frontend/src/js/tools/shapes/callout.js b/frontend/src/js/tools/shapes/callout.js new file mode 100644 index 0000000..761efdf --- /dev/null +++ b/frontend/src/js/tools/shapes/callout.js @@ -0,0 +1,99 @@ +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Callout_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'callout'; + this.layer = {}; + this.best_ratio = 1.3; + this.snap_line_info = {x: null, y: null}; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + ctx.fillStyle = '#aaa'; + ctx.strokeStyle = '#555'; + ctx.lineWidth = 2; + + var width_all = width + x * 2; + width = height * this.best_ratio; + x = (width_all - width) / 2; + + ctx.save(); + ctx.translate(x + width / 2, y + height / 2); + this.draw_shape(ctx, -width / 2, -height / 2, width, height); + ctx.restore(); + } + + render(ctx, layer) { + var params = layer.params; + var fill = params.fill; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height); + + ctx.restore(); + } + + draw_shape(ctx, x, y, width, height, coords) { + ctx.lineJoin = "round"; + + ctx.beginPath(); + + ctx.moveTo(x, y); + ctx.lineTo(x + width, y); + ctx.lineTo(x + width, y + height * 0.6); + + ctx.lineTo(x + width / 2 + width / 10, y + height * 0.6); + ctx.lineTo(x + width / 8, y + height); + ctx.lineTo(x + width / 2 - width / 10, y + height * 0.6); + + ctx.lineTo(x, y + height * 0.6); + ctx.lineTo(x, y); + + ctx.closePath(); + + ctx.fill(); + ctx.stroke(); + } + +} + +export default Callout_class; diff --git a/frontend/src/js/tools/shapes/cog.js b/frontend/src/js/tools/shapes/cog.js new file mode 100644 index 0000000..6030f5c --- /dev/null +++ b/frontend/src/js/tools/shapes/cog.js @@ -0,0 +1,82 @@ +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Cog_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'cog'; + this.layer = {}; + this.best_ratio = 1; + this.snap_line_info = {x: null, y: null}; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + ctx.fillStyle = '#777'; + ctx.lineWidth = 1; + + ctx.save(); + ctx.translate(x + width / 2, y + height / 2); + this.draw_shape(ctx, -width / 2, -height / 2, width, height); + ctx.restore(); + } + + render(ctx, layer) { + var params = layer.params; + + ctx.save(); + + //set styles + ctx.fillStyle = params.fill_color; + ctx.lineWidth = 1; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height); + + ctx.restore(); + } + + draw_shape(ctx, x, y, width, height, coords) { + ctx.lineJoin = "round"; + ctx.beginPath(); + + //better dont do this, there will be issues with border size + ctx.scale(width/512, height/500); + ctx.translate(-256, -252); + + //SVG path + var p = new Path2D("M190.883 502.932c-4.517 0-9.082-.991-13.368-3.055l-63.216-30.438c-13.348-6.426-20.255-21.479-16.422-35.794 3.684-13.757 8.609-29.81 14.376-46.879a195.425 195.425 0 0 1-15.733-19.711c-17.979 1.837-34.736 3.07-48.937 3.594-14.773.515-27.899-9.536-31.195-23.975L.776 278.273c-3.297-14.444 4.167-29.229 17.748-35.156 13.056-5.697 28.669-11.851 45.59-17.977a193.78 193.78 0 0 1 5.601-24.614c-12.655-12.922-24.061-25.246-33.297-35.989-9.643-11.217-9.939-27.761-.706-39.339l43.744-54.854c9.239-11.584 25.453-14.963 38.552-8.043 12.556 6.636 27.096 15.004 42.466 24.433a194.25 194.25 0 0 1 22.746-10.969c2.209-17.923 4.735-34.522 7.381-48.468 2.757-14.532 15.506-25.079 30.315-25.079h70.161c14.815 0 27.569 10.567 30.325 25.126 2.646 13.983 5.17 30.564 7.377 48.422a193.854 193.854 0 0 1 22.75 10.971c15.42-9.466 29.975-17.843 42.506-24.458 13.079-6.901 29.275-3.512 38.509 8.066l43.743 54.855c9.237 11.582 8.928 28.142-.738 39.374-9.254 10.756-20.646 23.066-33.263 35.957a193.79 193.79 0 0 1 5.601 24.62c16.986 6.145 32.615 12.304 45.634 17.992h.001c13.553 5.923 20.997 20.699 17.701 35.137l-15.615 68.4c-3.299 14.446-16.455 24.532-31.247 23.972-14.229-.531-30.97-1.762-48.889-3.588a195.251 195.251 0 0 1-15.728 19.703c5.791 17.122 10.723 33.189 14.394 46.921 3.819 14.291-3.093 29.324-16.436 35.748l-63.214 30.438c-13.351 6.428-29.426 2.438-38.224-9.484-8.455-11.455-17.931-25.313-27.679-40.466-8.425.548-16.745.548-25.176 0-9.772 15.201-19.257 29.075-27.702 40.508-5.964 8.075-15.283 12.499-24.824 12.5zm-61.851-61.915l61.516 29.619c15.437-20.988 29.097-42.937 36.43-54.579 26.665 3.104 31.829 3.053 58.035.001 6.932 10.997 20.8 33.291 36.445 54.576l61.515-29.619c-6.794-25.207-15.471-49.669-19.957-62.54 19.028-18.834 22.066-22.637 36.219-45.367 13.048 1.451 39.007 4.495 65.388 5.533l15.195-66.562c-24.034-10.441-48.695-18.946-61.337-23.387-2.824-26.58-3.888-31.341-12.882-56.619 9.27-9.299 27.886-27.753 45.083-47.657l-42.566-53.381c-22.622 12.001-44 25.528-56.513 33.37-22.495-14.328-26.889-16.481-52.31-25.228-1.474-12.904-4.292-38.972-9.156-64.958H221.86c-4.53 24.145-7.144 47.395-9.144 64.955-25.185 8.667-29.587 10.755-52.309 25.223-11.055-6.923-33.256-21.009-56.521-33.362l-42.568 53.379c16.896 19.57 35.133 37.669 45.088 47.647-8.943 25.131-10.043 29.878-12.885 56.613-12.366 4.348-37.104 12.879-61.339 23.397l15.192 66.562c25.642-.998 50.721-3.907 65.381-5.542 14.147 22.727 17.192 26.54 36.221 45.377-4.265 12.257-13.059 37.034-19.944 62.549zm351.667-168.554l.009.004-.009-.004zM256 347.486c-50.446 0-91.486-41.041-91.486-91.486s41.041-91.486 91.486-91.486c50.445 0 91.486 41.041 91.486 91.486S306.445 347.486 256 347.486zm0-150.972c-32.801 0-59.486 26.686-59.486 59.486S223.2 315.486 256 315.486 315.486 288.8 315.486 256 288.801 196.514 256 196.514z"); + + ctx.closePath(); + ctx.fill(p); + } + +} + +export default Cog_class; diff --git a/frontend/src/js/tools/shapes/cylinder.js b/frontend/src/js/tools/shapes/cylinder.js new file mode 100644 index 0000000..a87cfc9 --- /dev/null +++ b/frontend/src/js/tools/shapes/cylinder.js @@ -0,0 +1,100 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Cylinder_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'cylinder'; + this.layer = {}; + this.best_ratio = 0.7; + this.snap_line_info = {x: null, y: null}; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + ctx.fillStyle = '#aaa'; + ctx.strokeStyle = '#555'; + ctx.lineWidth = 2; + + var width_all = width + x * 2; + width = height * this.best_ratio; + x = (width_all - width) / 2; + + ctx.save(); + ctx.translate(x + width / 2, y + height / 2); + this.draw_shape(ctx, -width / 2, -height / 2, width, height); + ctx.restore(); + } + + render(ctx, layer) { + var params = layer.params; + var fill = params.fill; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height); + + ctx.restore(); + } + + draw_shape(ctx, x, y, width, height, coords) { + ctx.lineJoin = "round"; + + ctx.beginPath(); + + ctx.scale(1, 1.20); + ctx.translate(-width / 2, -height / 2); + + var dh = height/3; + + ctx.moveTo(0, dh); + ctx.bezierCurveTo(0,dh+dh, width,dh+dh, width,dh); + ctx.bezierCurveTo(width,dh-dh, 0,dh-dh, 0,dh); + ctx.lineTo(0, height-dh); + ctx.bezierCurveTo(0,height-dh+dh, width,height-dh+dh, width,height-dh); + ctx.lineTo(width, dh); + + ctx.fill(); + ctx.stroke(); + } + +} + +export default Cylinder_class; diff --git a/frontend/src/js/tools/shapes/ellipse.js b/frontend/src/js/tools/shapes/ellipse.js new file mode 100644 index 0000000..a5d3fc4 --- /dev/null +++ b/frontend/src/js/tools/shapes/ellipse.js @@ -0,0 +1,262 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Ellipse_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'ellipse'; + this.layer = {}; + this.best_ratio = 1; + this.snap_line_info = {x: null, y: null}; + this.mouse_click = {x: null, y: null}; + } + + load() { + this.default_events(); + } + + mousedown(e) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.click_valid == false) + return; + + var mouse_x = mouse.x; + var mouse_y = mouse.y; + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + + this.mouse_click.x = mouse_x; + this.mouse_click.y = mouse_y; + + //register new object - current layer is not ours or params changed + this.layer = { + type: this.name, + params: this.clone(this.getParams()), + status: 'draft', + render_function: [this.name, 'render'], + x: mouse_x, + y: mouse_y, + color: null, + is_vector: true, + }; + if (params.circle == true) { + //disable rotate + this.layer.rotate = null; + } + app.State.do_action( + new app.Actions.Bundle_action('new_ellipse_layer', 'New Ellipse Layer', [ + new app.Actions.Insert_layer_action(this.layer) + ]) + ); + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + + var mouse_x = Math.round(mouse.x); + var mouse_y = Math.round(mouse.y); + var click_x = Math.round(this.mouse_click.x); + var click_y = Math.round(this.mouse_click.y); + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y, config.layer.id); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + + var x = Math.min(mouse_x, click_x); + var y = Math.min(mouse_y, click_y); + var width = Math.abs(mouse_x - click_x); + var height = Math.abs(mouse_y - click_y); + + if (params.circle == true || e.ctrlKey == true || e.metaKey) { + if (width < height) { + width = height; + } + else { + height = width; + } + if (mouse_x < click_x) { + x = click_x - width; + } + if (mouse_y < click_y) { + y = click_y - height; + } + } + + //more data + config.layer.x = x; + config.layer.y = y; + config.layer.width = width; + config.layer.height = height; + + this.Base_layers.render(); + } + + mouseup(e) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + + if (mouse.click_valid == false) { + config.layer.status = null; + return; + } + + var mouse_x = Math.round(mouse.x); + var mouse_y = Math.round(mouse.y); + var click_x = Math.round(this.mouse_click.x); + var click_y = Math.round(this.mouse_click.y); + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y, config.layer.id); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + this.snap_line_info = {x: null, y: null}; + + var x = Math.min(mouse_x, click_x); + var y = Math.min(mouse_y, click_y); + var width = Math.abs(mouse_x - click_x); + var height = Math.abs(mouse_y - click_y); + + if (params.circle == true || e.ctrlKey == true || e.metaKey) { + if (width < height) { + width = height; + } + else { + height = width; + } + if (mouse_x < click_x) { + x = click_x - width; + } + if (mouse_y < click_y) { + y = click_y - height; + } + } + + if (width == 0 && height == 0) { + //same coordinates - cancel + app.State.scrap_last_action(); + return; + } + + //more data + app.State.do_action( + new app.Actions.Update_layer_action(config.layer.id, { + x: x, + y: y, + width: width, + height: height, + status: null + }), + { merge_with_history: 'new_ellipse_layer' } + ); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + x = parseInt(x); + y = parseInt(y); + width = parseInt(width); + height = parseInt(height); + + ctx.fillStyle = '#aaa'; + ctx.strokeStyle = '#555'; + ctx.lineWidth = 3; + + this.ellipse( + ctx, + x, + y, + width, + height, + true, + true + ); + } + + render(ctx, layer) { + var params = layer.params; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + var dist_x = layer.width; + var dist_y = layer.height; + + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.ellipse(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height, params.border, params.fill); + + ctx.restore(); + } + + ellipse(ctx, x, y, w, h, stroke, fill) { + var kappa = .5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + ctx.beginPath(); + ctx.moveTo(x, ym); + ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + ctx.closePath(); + if ( stroke == true) + ctx.stroke(); + if (fill == true) + ctx.fill(); + } + +} + +export default Ellipse_class; diff --git a/frontend/src/js/tools/shapes/heart.js b/frontend/src/js/tools/shapes/heart.js new file mode 100644 index 0000000..bfb744d --- /dev/null +++ b/frontend/src/js/tools/shapes/heart.js @@ -0,0 +1,106 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Heart_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'heart'; + this.layer = {}; + this.best_ratio = 1.2; + this.snap_line_info = {x: null, y: null}; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + ctx.fillStyle = '#aaa'; + ctx.strokeStyle = '#555'; + ctx.lineWidth = 2; + + ctx.save(); + ctx.translate(x + width / 2, y + height / 2); + this.draw_shape(ctx, -width / 2, -height / 2, width, height); + ctx.restore(); + } + + render(ctx, layer) { + var params = layer.params; + var fill = params.fill; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height); + + ctx.restore(); + } + + draw_shape(ctx, x, y, width, height, coords) { + ctx.lineJoin = "round"; + + ctx.beginPath(); + + ctx.scale(1.071, 1.1); + ctx.translate(-width / 2, -height / 1.85); + + ctx.moveTo(width/2, height/5); + ctx.bezierCurveTo(5 * width / 14, 0, + 0, height / 15, + width / 28, 2 * height / 5); + + ctx.bezierCurveTo(width / 14, 2 * height / 3, + 3 * width / 7, 5 * height / 6, + width / 2, height); + + ctx.bezierCurveTo(4 * width / 7, 5 * height / 6, + 13 * width / 14, 2 * height / 3, + 27 * width / 28, 2 * height / 5); + + ctx.bezierCurveTo(width, height / 15, + 9 * width / 14, 0, + width / 2, height / 5); + + ctx.closePath(); + + ctx.fill(); + ctx.stroke(); + } + +} + +export default Heart_class; diff --git a/frontend/src/js/tools/shapes/hexagon.js b/frontend/src/js/tools/shapes/hexagon.js new file mode 100644 index 0000000..ead2442 --- /dev/null +++ b/frontend/src/js/tools/shapes/hexagon.js @@ -0,0 +1,114 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Hexagon_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'hexagon'; + this.layer = {}; + this.best_ratio = 1.1547005; + this.snap_line_info = {x: null, y: null}; + this.coords = [ + [75, 6.698729810778069], + [100, 50], + [75, 93.30127018922192], + [24.99999999999999, 93.30127018922192], + [0, 50.00000000000001], + [24.99999999999998, 6.698729810778076], + [75, 6.698729810778069], + [75, 6.698729810778069], + ]; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + ctx.fillStyle = '#aaa'; + ctx.strokeStyle = '#555'; + ctx.lineWidth = 2; + + this.draw_shape(ctx, x, y - 5, width, height, this.coords); + + } + + render(ctx, layer) { + var params = layer.params; + var fill = params.fill; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height, this.coords); + + ctx.restore(); + } + + draw_shape(ctx, x, y, width, height, coords) { + ctx.lineJoin = "round"; + + ctx.beginPath(); + + ctx.scale(1, this.best_ratio); + + for(var i in coords){ + if(coords[i] === null){ + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + continue; + } + + //coords in 100x100 box + var pos_x = x + coords[i][0] * width / 100; + var pos_y = y + coords[i][1] * height / 100; + + if(i == '0') + ctx.moveTo(pos_x, pos_y); + else + ctx.lineTo(pos_x, pos_y); + } + ctx.closePath(); + + ctx.fill(); + ctx.stroke(); + } + +} + +export default Hexagon_class; diff --git a/frontend/src/js/tools/shapes/human.js b/frontend/src/js/tools/shapes/human.js new file mode 100644 index 0000000..07a2653 --- /dev/null +++ b/frontend/src/js/tools/shapes/human.js @@ -0,0 +1,110 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Human_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'human'; + this.layer = {}; + this.best_ratio = 0.35; + this.snap_line_info = {x: null, y: null}; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + ctx.fillStyle = '#aaa'; + ctx.strokeStyle = '#555'; + ctx.lineWidth = 2; + + var width_all = width + x * 2; + width = height * this.best_ratio; + x = (width_all - width) / 2; + + ctx.save(); + ctx.translate(x + width / 2, y + height / 2); + this.draw_shape(ctx, -width / 2, -height / 2, width, height); + ctx.restore(); + } + + render(ctx, layer) { + var params = layer.params; + var fill = params.fill; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height); + + ctx.restore(); + } + + draw_shape(ctx, x, y, width, height) { + ctx.lineJoin = "round"; + + ctx.beginPath(); + + ctx.translate(-width / 2, -height / 2); + + var radius = Math.sqrt(width * height) * 0.28; + var neck_height = height * 0.07; + var leg_height = height * 0.3; + if(radius * 2 + neck_height + leg_height > height){ + radius = (height - leg_height - neck_height) / 2; + } + + ctx.arc(width / 2, radius, radius, 0, 2 * Math.PI); + //body + ctx.moveTo(width / 2, radius * 2); + ctx.lineTo(width / 2, height - leg_height); + //arm + ctx.moveTo(0, radius*2 + neck_height); + ctx.lineTo(width, radius*2 + neck_height); + //left leg + ctx.moveTo(width / 2, height - leg_height); + ctx.lineTo(0, height); + //right leg + ctx.moveTo(width / 2, height - leg_height); + ctx.lineTo(width, height); + + ctx.fill(); + ctx.stroke(); + } + +} + +export default Human_class; diff --git a/frontend/src/js/tools/shapes/line.js b/frontend/src/js/tools/shapes/line.js new file mode 100644 index 0000000..d1fd061 --- /dev/null +++ b/frontend/src/js/tools/shapes/line.js @@ -0,0 +1,195 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Line_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'line'; + this.layer = {}; + this.best_ratio = 1; + this.snap_line_info = {x: null, y: null}; + this.mouse_click = {x: null, y: null}; + } + + load() { + this.default_events(); + } + + mousedown(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) + return; + + var mouse_x = mouse.x; + var mouse_y = mouse.y; + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + + this.mouse_click.x = mouse_x; + this.mouse_click.y = mouse_y; + + //register new object - current layer is not ours or params changed + this.layer = { + type: this.name, + params: this.clone(this.getParams()), + status: 'draft', + render_function: [this.name, 'render'], + x: mouse_x, + y: mouse_y, + rotate: null, + is_vector: true, + color: config.COLOR + }; + app.State.do_action( + new app.Actions.Bundle_action('new_line_layer', 'New Line Layer', [ + new app.Actions.Insert_layer_action(this.layer) + ]) + ); + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + + var mouse_x = Math.round(mouse.x); + var mouse_y = Math.round(mouse.y); + var click_x = Math.round(this.mouse_click.x); + var click_y = Math.round(this.mouse_click.y); + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y, config.layer.id); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + + var width = mouse_x - this.layer.x; + var height = mouse_y - this.layer.y; + if (e.ctrlKey == true || e.metaKey) { + //one direction only + if (Math.abs(width) < Math.abs(height)) + width = 0; + else + height = 0; + } + + //more data + config.layer.width = width; + config.layer.height = height; + + this.Base_layers.render(); + } + + mouseup(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) { + config.layer.status = null; + return; + } + + var mouse_x = Math.round(mouse.x); + var mouse_y = Math.round(mouse.y); + var click_x = Math.round(this.mouse_click.x); + var click_y = Math.round(this.mouse_click.y); + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y, config.layer.id); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + this.snap_line_info = {x: null, y: null}; + + + var width = mouse_x - this.layer.x; + var height = mouse_y - this.layer.y; + + if (width == 0 && height == 0) { + //same coordinates - cancel + app.State.scrap_last_action(); + return; + } + + if (e.ctrlKey == true || e.metaKey) { + //one direction only + if (Math.abs(width) < Math.abs(height)) + width = 0; + else + height = 0; + } + + //more data + app.State.do_action( + new app.Actions.Update_layer_action(config.layer.id, { + width, + height, + status: null + }), + { merge_with_history: 'new_line_layer' } + ); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + var coords = [ + [0, 0], + [100, 100], + ]; + this.draw_shape(ctx, x, y, width, height, coords); + } + + render(ctx, layer) { + if (layer.width == 0 && layer.height == 0) + return; + + var params = layer.params; + + //set styles + ctx.fillStyle = layer.color; + ctx.strokeStyle = layer.color; + ctx.lineWidth = params.size; + ctx.lineCap = 'round'; + + var width = layer.x + layer.width; + var height = layer.y + layer.height; + + //draw line + ctx.beginPath(); + ctx.moveTo(layer.x, layer.y); + ctx.lineTo(width, height); + ctx.stroke(); + } + +} + +export default Line_class; diff --git a/frontend/src/js/tools/shapes/moon.js b/frontend/src/js/tools/shapes/moon.js new file mode 100644 index 0000000..8e57333 --- /dev/null +++ b/frontend/src/js/tools/shapes/moon.js @@ -0,0 +1,120 @@ +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Moon_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'moon'; + this.layer = {}; + this.best_ratio = 0.8; + this.snap_line_info = {x: null, y: null}; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + ctx.fillStyle = '#aaa'; + ctx.strokeStyle = '#555'; + ctx.lineWidth = 2; + + var width_all = width + x * 2; + width = height * this.best_ratio; + x = (width_all - width) / 2; + + ctx.save(); + ctx.translate(x + width / 2, y + height / 2); + this.draw_shape(ctx, -width / 2, -height / 2, width, height, true, true); + ctx.restore(); + } + + render(ctx, layer) { + var params = layer.params; + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height, params.fill, params.border); + + ctx.restore(); + } + + draw_shape(ctx, x, y, width, height, fill, stroke) { + var left = parseInt(x); + var top = parseInt(y); + + ctx.beginPath(); + ctx.moveTo(left + width * 0.512, top + height / 2); + ctx.bezierCurveTo( + left + width * 51.2 / 100, top + height * 28.4 / 100, + left + width * 71.5 / 100, top + height * 10.1 / 100, + left + width * 100 / 100, top + height * 3.1 / 100 + ); + ctx.bezierCurveTo( + left + width * 92 / 100, top + height * 1.1 / 100, + left + width * 83.4 / 100, top + height * 0 / 100, + left + width * 74.4 / 100, top + height * 0 / 100 + ); + ctx.bezierCurveTo( + left + width * 33.3 / 100, top + height * 0 / 100, + left + width * 0 / 100, top + height * 22.4 / 100, + left + width * 0 / 100, top + height * 50 / 100 + ); + ctx.bezierCurveTo( + left + width * 0 / 100, top + height * 77.6 / 100, + left + width * 33.3 / 100, top + height * 100 / 100, + left + width * 74.4 / 100, top + height * 100 / 100 + ); + ctx.bezierCurveTo( + left + width * 83.4 / 100, top + height * 100 / 100, + left + width * 92 / 100, top + height * 98.9 / 100, + left + width * 100 / 100, top + height * 96.9 / 100 + ); + ctx.bezierCurveTo( + left + width * 71.5 / 100, top + height * 89.9 / 100, + left + width * 51.2 / 100, top + height * 71.6 / 100, + left + width * 51.2 / 100, top + height * 50 / 100 + ); + ctx.closePath(); + if (fill) { + ctx.fill(); + } + if (stroke) { + ctx.stroke(); + } + } + +} + +export default Moon_class; diff --git a/frontend/src/js/tools/shapes/parallelogram.js b/frontend/src/js/tools/shapes/parallelogram.js new file mode 100644 index 0000000..5ab9258 --- /dev/null +++ b/frontend/src/js/tools/shapes/parallelogram.js @@ -0,0 +1,75 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Parallelogram_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'parallelogram'; + this.layer = {}; + this.best_ratio = 2; + this.snap_line_info = {x: null, y: null}; + this.coords = [ + [25, 0], + [100, 0], + [75, 100], + [0, 100], + [25, 0], + ]; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + this.draw_shape(ctx, x, y, width, height, this.coords); + } + + render(ctx, layer) { + var params = layer.params; + var fill = params.fill; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height, this.coords, false); + + ctx.restore(); + } + +} + +export default Parallelogram_class; diff --git a/frontend/src/js/tools/shapes/pentagon.js b/frontend/src/js/tools/shapes/pentagon.js new file mode 100644 index 0000000..56e8047 --- /dev/null +++ b/frontend/src/js/tools/shapes/pentagon.js @@ -0,0 +1,112 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Pentagon_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'pentagon'; + this.layer = {}; + this.best_ratio = 1.051; + this.snap_line_info = {x: null, y: null}; + this.coords = [ + [100.40599536364314, 38.90073974812779], + [81.15261837150108, 98.1565411518722], + [18.84738162849893, 98.1565411518722], + [-0.40599536364314304, 38.90073974812779], + [49.99999999999999, 2.2786404499999975], + [100.40599536364314, 38.900739748127776], + [100.40599536364314, 38.90073974812779], + ]; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + ctx.fillStyle = '#aaa'; + ctx.strokeStyle = '#555'; + ctx.lineWidth = 2; + + this.draw_shape(ctx, x, y, width, height, this.coords); + } + + render(ctx, layer) { + var params = layer.params; + var fill = params.fill; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height, this.coords); + + ctx.restore(); + } + + draw_shape(ctx, x, y, width, height, coords) { + ctx.lineJoin = "round"; + + ctx.beginPath(); + + ctx.scale(1, 1.051); + + for(var i in coords){ + if(coords[i] === null){ + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + continue; + } + + //coords in 100x100 box + var pos_x = x + coords[i][0] * width / 100; + var pos_y = y + coords[i][1] * height / 100; + + if(i == '0') + ctx.moveTo(pos_x, pos_y); + else + ctx.lineTo(pos_x, pos_y); + } + ctx.closePath(); + + ctx.fill(); + ctx.stroke(); + } + +} + +export default Pentagon_class; diff --git a/frontend/src/js/tools/shapes/plus.js b/frontend/src/js/tools/shapes/plus.js new file mode 100644 index 0000000..e238ee4 --- /dev/null +++ b/frontend/src/js/tools/shapes/plus.js @@ -0,0 +1,85 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Helper_class from './../../libs/helpers.js'; + +class Plus_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.ctx = ctx; + this.name = 'plus'; + this.layer = {}; + this.best_ratio = 1; + this.snap_line_info = {x: null, y: null}; + this.coords = [ + [35, 0], + [65, 0], + [65, 35], + [100, 35], + [100, 65], + [65, 65], + [65, 100], + [35, 100], + [35, 65], + [0, 65], + [0, 35], + [35, 35], + [35, 0], + ]; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + this.draw_shape(ctx, x, y, width, height, this.coords); + } + + render(ctx, layer) { + var params = layer.params; + var fill = params.fill; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height, this.coords, false); + + ctx.restore(); + } + +} + +export default Plus_class; diff --git a/frontend/src/js/tools/shapes/polygon.js b/frontend/src/js/tools/shapes/polygon.js new file mode 100644 index 0000000..f4576fb --- /dev/null +++ b/frontend/src/js/tools/shapes/polygon.js @@ -0,0 +1,369 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; +import Helper_class from './../../libs/helpers.js'; + +class Polygon_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.ctx = ctx; + this.name = 'polygon'; + this.layer = {}; + this.best_ratio = 1; + this.snap_line_info = {x: null, y: null}; + this.params_hash = false; + this.selected_obj_positions = {}; + this.mouse_lock = null; + this.selected_object_drag_type = null; + this.old_data = null; + + this.events(); + } + + load() { + var _this = this; + this.default_events(); + document.addEventListener('keydown', function (event) { + var code = event.code; + if (config.TOOL.name == _this.name && code == "Escape") { + //escape + config.layer.status = null; + } + }); + } + + /** + * events for handling helping lines only + */ + events() { + document.addEventListener('mousedown', (e) => { + this.selected_object_actions(e); + }); + document.addEventListener('mousemove', (e) => { + this.selected_object_actions(e); + }); + document.addEventListener('mouseup', (e) => { + this.selected_object_actions(e); + }); + + // touch + document.addEventListener('touchstart', (event) => { + this.selected_object_actions(event); + }); + document.addEventListener('touchmove', (event) => { + this.selected_object_actions(event); + }, {passive: false}); + document.addEventListener('touchend', (event) => { + this.selected_object_actions(event); + }); + } + + mousedown(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) { + return; + } + + var params_hash = this.get_params_hash(); + + var mouse_x = mouse.x; + var mouse_y = mouse.y; + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + + if (config.layer.type != this.name || params_hash != this.params_hash + || (config.layer.data != null && config.layer.status != 'draft')) { + //register new object - current layer is not ours or params changed + this.layer = { + type: this.name, + data: [ + {x: mouse_x, y: mouse_y} + ], + params: this.clone(this.getParams()), + render_function: [this.name, 'render'], + x: 0, + y: 0, + width: null, + height: null, + hide_selection_if_active: true, + rotate: null, + is_vector: true, + color: null, + status: 'draft', + }; + app.State.do_action( + new app.Actions.Bundle_action('new_polygon_layer', 'New Polygon Layer', [ + new app.Actions.Insert_layer_action(this.layer) + ]) + ); + this.params_hash = params_hash; + } + else { + //add more data + config.layer.data.push( + {x: mouse_x, y: mouse_y} + ); + } + + this.Base_layers.render(); + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + var mouse_x = Math.round(mouse.x); + var mouse_y = Math.round(mouse.y); + + if (mouse.click_valid == false) { + return; + } + if (mouse.is_drag == false) { + return; + } + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y, config.layer.id); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + + //add more data + config.layer.data[config.layer.data.length - 1] = {x: mouse_x, y: mouse_y}; + + this.Base_layers.render(); + } + + mouseup(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) { + return; + } + + var mouse_x = Math.round(mouse.x); + var mouse_y = Math.round(mouse.y); + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y, config.layer.id); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + this.snap_line_info = {x: null, y: null}; + + //add more data + config.layer.data[config.layer.data.length - 1] = {x: mouse_x, y: mouse_y}; + + this.Base_layers.render(); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + + if(config.TOOL.name != 'select'){ + return; + } + + //also draw control lines + if(config.layer.type == this.name){ + var data = config.layer.data; + this.selected_obj_positions = {}; + + //draw corners + for(var i in data) { + var point = data[i]; + + this.selected_obj_positions[i] = this.Helper.draw_control_point( + this.ctx, + config.layer.x + point.x, + config.layer.y + point.y + ); + } + } + } + + select(ctx) { + this.render_overlay(ctx); + } + + demo(ctx, x, y, width, height) { + ctx.fillStyle = '#aaa'; + ctx.strokeStyle = '#555'; + ctx.lineWidth = 2; + + var width_all = width + x * 2; + width = height * this.best_ratio; + x = (width_all - width) / 2; + + var data = [ + {x: 0, y: 0}, + {x: width, y: 0}, + {x: width * 1.1, y: height * 2 / 3}, + {x: width / 2, y: height / 3}, + {x: -1 * width * 0.2, y: height}, + ]; + + ctx.save(); + ctx.translate(x + width / 2, y + height / 2); + this.draw_polygon(ctx, -width / 2, -height / 2, width, height, data); + ctx.restore(); + } + + render(ctx, layer) { + var params = layer.params; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_polygon(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height, layer.data); + + ctx.restore(); + } + + draw_polygon(ctx, x, y, width, height, data) { + if(data.length == 0){ + return; + } + + //draw + ctx.beginPath(); + for(var i in data) { + if(i == 0){ + ctx.moveTo(x + data[i].x, y + data[i].y); + } + else{ + ctx.lineTo(x + data[i].x, y + data[i].y); + } + } + ctx.closePath(); + ctx.fill() + ctx.stroke(); + } + + selected_object_actions(e) { + if(config.TOOL.name != 'select' || config.layer.type != this.name){ + return; + } + + var ctx = this.Base_layers.ctx; + var mouse = this.get_mouse_info(e); + const mainWrapper = document.getElementById('main_wrapper'); + + //simplify checks + var event_type = e.type; + if(event_type == 'touchstart') event_type = 'mousedown'; + if(event_type == 'touchmove') event_type = 'mousemove'; + if(event_type == 'touchend') event_type = 'mouseup'; + + if (event_type == 'mouseup') { + //reset + config.mouse_lock = null; + if (mainWrapper.style.cursor != 'default') { + mainWrapper.style.cursor = 'default'; + } + } + + if (event_type == 'mousedown' && config.mouse.valid == false) { + return; + } + + if (event_type == 'mousemove' && this.mouse_lock == 'move_point' && mouse.is_drag) { + mainWrapper.style.cursor = "move"; + + if (e.buttons == 1 || typeof e.buttons == "undefined") { + var type = this.selected_object_drag_type; + var bezier = config.layer.data; + + // Do transformations + var dx = Math.round(mouse.x - mouse.click_x) - config.layer.x; + var dy = Math.round(mouse.y - mouse.click_y) - config.layer.y; + + // Set values + config.layer.data[type] = { + x: mouse.click_x + dx, + y: mouse.click_y + dy + }; + + config.need_render = true; + } + return; + } + if (event_type == 'mouseup' && this.mouse_lock == 'move_point') { + this.mouse_lock = null; + var bezier = config.layer.data; + + //reset sate + config.layer.data = this.old_data; + + //save state + app.State.do_action( + new app.Actions.Bundle_action('change_layer_details', 'Change Layer Details', [ + new app.Actions.Update_layer_action(config.layer.id, { + data: bezier, + }) + ]) + ); + + config.need_render = true; + } + + if (!mouse.is_drag && ['mousedown', 'mouseup'].includes(event_type)) { + return; + } + + if (!this.mouse_lock) { + for (let current_drag_type in this.selected_obj_positions) { + const position = this.selected_obj_positions[current_drag_type]; + if (position && this.ctx.isPointInPath(position, mouse.x, mouse.y)) { + // match + if (event_type == 'mousedown') { + if (e.buttons == 1 || typeof e.buttons == "undefined") { + this.mouse_lock = 'move_point'; + this.selected_object_drag_type = current_drag_type; + } + config.mouse_lock = true; + this.old_data = JSON.parse(JSON.stringify(config.layer.data)); + } + if (event_type == 'mousemove') { + mainWrapper.style.cursor = 'move'; + } + } + } + } + } + +} + +export default Polygon_class; diff --git a/frontend/src/js/tools/shapes/rectangle.js b/frontend/src/js/tools/shapes/rectangle.js new file mode 100644 index 0000000..0102f4e --- /dev/null +++ b/frontend/src/js/tools/shapes/rectangle.js @@ -0,0 +1,302 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Rectangle_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'rectangle'; + this.layer = {}; + this.best_ratio = 1; + this.snap_line_info = {x: null, y: null}; + this.mouse_click = {x: null, y: null}; + } + + load() { + this.default_events(); + } + + mousedown(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) + return; + + var mouse_x = mouse.x; + var mouse_y = mouse.y; + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + + this.mouse_click.x = mouse_x; + this.mouse_click.y = mouse_y; + + //register new object - current layer is not ours or params changed + this.layer = { + type: this.name, + params: this.clone(this.getParams()), + status: 'draft', + render_function: [this.name, 'render'], + x: Math.round(mouse_x), + y: Math.round(mouse_y), + color: null, + is_vector: true + }; + app.State.do_action( + new app.Actions.Bundle_action('new_rectangle_layer', 'New Rectangle Layer', [ + new app.Actions.Insert_layer_action(this.layer) + ]) + ); + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + + var mouse_x = Math.round(mouse.x); + var mouse_y = Math.round(mouse.y); + var click_x = Math.round(this.mouse_click.x); + var click_y = Math.round(this.mouse_click.y); + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y, config.layer.id); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + + var x = Math.min(mouse_x, click_x); + var y = Math.min(mouse_y, click_y); + var width = Math.abs(mouse_x - click_x); + var height = Math.abs(mouse_y - click_y); + + if (params.square == true || e.ctrlKey == true || e.metaKey) { + if (width < height) { + width = height; + } + else { + height = width; + } + if (mouse_x < click_x) { + x = click_x - width; + } + if (mouse_y < click_y) { + y = click_y - height; + } + } + + //more data + config.layer.x = x; + config.layer.y = y; + config.layer.width = width; + config.layer.height = height; + + this.Base_layers.render(); + } + + mouseup(e) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + + if (mouse.click_valid == false) { + config.layer.status = null; + return; + } + + var mouse_x = Math.round(mouse.x); + var mouse_y = Math.round(mouse.y); + var click_x = Math.round(this.mouse_click.x); + var click_y = Math.round(this.mouse_click.y); + + //apply snap + var snap_info = this.calc_snap_position(e, mouse_x, mouse_y, config.layer.id); + if(snap_info != null){ + if(snap_info.x != null) { + mouse_x = snap_info.x; + } + if(snap_info.y != null) { + mouse_y = snap_info.y; + } + } + this.snap_line_info = {x: null, y: null}; + + var x = Math.min(mouse_x, click_x); + var y = Math.min(mouse_y, click_y); + var width = Math.abs(mouse_x - click_x); + var height = Math.abs(mouse_y - click_y); + + if (params.square == true || e.ctrlKey == true || e.metaKey) { + if (width < height) { + width = height; + } + else { + height = width; + } + if (mouse_x < click_x) { + x = click_x - width; + } + if (mouse_y < click_y) { + y = click_y - height; + } + } + + if (width == 0 && height == 0) { + //same coordinates - cancel + app.State.scrap_last_action(); + return; + } + + //more data + app.State.do_action( + new app.Actions.Update_layer_action(config.layer.id, { + x, + y, + width, + height, + status: null + }), + { merge_with_history: 'new_rectangle_layer' } + ); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + var coords = [ + [0, 0], + [100, 0], + [100, 100], + [0, 100], + [0, 0], + ]; + this.draw_shape(ctx, x, y, width, height, coords); + } + + render(ctx, layer) { + var params = layer.params; + var fill = params.fill; + var stroke = params.border; + var rotateSupport = true; + var radius = params.radius; + if(radius == undefined) + radius = 0; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + if (rotateSupport == false) { + this.roundRect(ctx, layer.x, layer.y, layer.width, layer.height, radius, fill, stroke); + } + else { + //rotate + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.roundRect(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height, radius, fill, stroke); + } + + ctx.restore(); + } + + /** + * Draws a rounded rectangle on canvas. + * + * @param {CanvasRenderingContext2D} ctx + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + * @param {Number} radius + * @param {Boolean} fill + */ + roundRect(ctx, x, y, width, height, radius, fill, stroke) { + x = parseInt(x); + y = parseInt(y); + width = parseInt(width); + height = parseInt(height); + if(width < 0){ + width = Math.abs(width); + x = x - width; + } + if(height < 0){ + height = Math.abs(height); + y = y - height; + } + var smaller_dimension = Math.min(width, height); + + radius = parseInt(radius); + if (typeof fill == 'undefined') { + fill = false; + } + if (typeof radius === 'undefined') { + radius = 0; + } + radius = Math.min(radius, width / 2, height / 2); + radius = Math.floor(radius); + + // Odd dimensions must draw offset half a pixel + if (width % 2 == 1 && config.layer.status != 'draft') { + x -= 0.5; + } + if (height % 2 == 1 && config.layer.status != 'draft') { + y -= 0.5; + } + + var stroke_offset = !fill && ctx.lineWidth % 2 == 1 && width > 1 && height > 1 ? 0.5 : 0; + + if (smaller_dimension < 2) fill = true; + + radius = {tl: radius, tr: radius, br: radius, bl: radius}; + ctx.beginPath(); + ctx.moveTo(x + radius.tl + stroke_offset, y + stroke_offset); + ctx.lineTo(x + width - radius.tr - stroke_offset, y + stroke_offset); + ctx.quadraticCurveTo(x + width - stroke_offset, y + stroke_offset, x + width - stroke_offset, y + radius.tr + stroke_offset); + ctx.lineTo(x + width - stroke_offset, y + height - radius.br - stroke_offset); + ctx.quadraticCurveTo(x + width - stroke_offset, y + height - stroke_offset, x + width - radius.br - stroke_offset, y + height - stroke_offset); + ctx.lineTo(x + radius.bl + stroke_offset, y + height - stroke_offset); + ctx.quadraticCurveTo(x + stroke_offset, y + height - stroke_offset, x + stroke_offset, y + height - radius.bl - stroke_offset); + ctx.lineTo(x + stroke_offset, y + radius.tl + stroke_offset); + ctx.quadraticCurveTo(x + stroke_offset, y + stroke_offset, x + radius.tl + stroke_offset, y + stroke_offset); + ctx.closePath(); + if (fill) { + ctx.fill(); + } + if (stroke) { + ctx.stroke(); + } + } + +} + +export default Rectangle_class; diff --git a/frontend/src/js/tools/shapes/right_triangle.js b/frontend/src/js/tools/shapes/right_triangle.js new file mode 100644 index 0000000..de85a81 --- /dev/null +++ b/frontend/src/js/tools/shapes/right_triangle.js @@ -0,0 +1,74 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Right_Triangle_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'right_triangle'; + this.layer = {}; + this.best_ratio = 1; + this.snap_line_info = {x: null, y: null}; + this.coords = [ + [0, 0], + [100, 100], + [0, 100], + [0, 0], + ]; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + this.draw_shape(ctx, x, y, width, height, this.coords); + } + + render(ctx, layer) { + var params = layer.params; + var fill = params.fill; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height, this.coords, false); + + ctx.restore(); + } + +} + +export default Right_Triangle_class; diff --git a/frontend/src/js/tools/shapes/romb.js b/frontend/src/js/tools/shapes/romb.js new file mode 100644 index 0000000..7b92e9d --- /dev/null +++ b/frontend/src/js/tools/shapes/romb.js @@ -0,0 +1,82 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Romb_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'romb'; + this.layer = {}; + this.best_ratio = 0.8; + this.snap_line_info = {x: null, y: null}; + this.coords_demo = [ + [50, 0], + [80, 50], + [50, 100], + [20, 50], + [50, 0], + ]; + this.coords = [ + [50, 0], + [100, 50], + [50, 100], + [0, 50], + [50, 0], + ]; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + this.draw_shape(ctx, x, y, width, height, this.coords_demo); + } + + render(ctx, layer) { + var params = layer.params; + var fill = params.fill; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height, this.coords, false); + + ctx.restore(); + } + +} + +export default Romb_class; diff --git a/frontend/src/js/tools/shapes/star.js b/frontend/src/js/tools/shapes/star.js new file mode 100644 index 0000000..93b190d --- /dev/null +++ b/frontend/src/js/tools/shapes/star.js @@ -0,0 +1,113 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Star_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'star'; + this.layer = {}; + this.best_ratio = 1; + this.coords = []; + this.snap_line_info = {x: null, y: null}; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + generate_coords(spikes, innerRadius) { + //settings + + innerRadius = parseInt(innerRadius) / 2; + innerRadius = Math.min(Math.max(innerRadius, 0), 100); + + spikes = parseInt(spikes); + spikes = Math.max(spikes, 3); + + var outerRadius = 50; + if(spikes == 5){ + outerRadius = 53; + } + + var cx = 50; + + var cy = 50; + if(spikes == 5){ + cy = 55; + } + + var rot = Math.PI / 2 * 3; + var x = cx; + var y = cy; + var step = Math.PI / spikes; + this.coords = []; + this.coords.push([cx, cy - outerRadius]); + for (var i = 0; i < spikes; i++) { + x = cx + Math.cos(rot) * outerRadius; + y = cy + Math.sin(rot) * outerRadius; + this.coords.push([x, y]); + rot += step; + + x = cx + Math.cos(rot) * innerRadius; + y = cy + Math.sin(rot) * innerRadius; + this.coords.push([x, y]); + rot += step; + } + this.coords.push([cx, cy - outerRadius]); + } + + demo(ctx, x, y, width, height) { + this.generate_coords(5, 40); + this.draw_shape(ctx, x, y, width, height, this.coords); + } + + render(ctx, layer) { + var params = layer.params; + var fill = params.fill; + + this.generate_coords(params.corners, params.inner_radius); + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height, this.coords, false); + + ctx.restore(); + } + +} + +export default Star_class; diff --git a/frontend/src/js/tools/shapes/tear.js b/frontend/src/js/tools/shapes/tear.js new file mode 100644 index 0000000..fbaead4 --- /dev/null +++ b/frontend/src/js/tools/shapes/tear.js @@ -0,0 +1,115 @@ +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Tear_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'tear'; + this.layer = {}; + this.best_ratio = 0.7; + this.snap_line_info = {x: null, y: null}; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + ctx.fillStyle = '#aaa'; + ctx.strokeStyle = '#555'; + ctx.lineWidth = 2; + + var width_all = width + x * 2; + width = height * this.best_ratio; + x = (width_all - width) / 2; + + ctx.save(); + ctx.translate(x + width / 2, y + height / 2); + this.draw_shape(ctx, -width / 2, -height / 2, width, height, true, true); + ctx.restore(); + } + + render(ctx, layer) { + var params = layer.params; + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height, params.fill, params.border); + + ctx.restore(); + } + + draw_shape(ctx, x, y, width, height, fill, stroke) { + var left = parseInt(x); + var top = parseInt(y); + + //settings + var curve_height = 29 / 100; + var curve_start_x = 28 / 100; + var curve_end_x = 1 - curve_start_x; + var curve_cdx = 70; + var curve_cdy = 58; + + ctx.beginPath(); + ctx.moveTo(left + width * 0.5, top); + ctx.quadraticCurveTo( + left + width * 0.5, top + height * 13 / 100, + left + width * curve_end_x, top + height * curve_height + ); + ctx.bezierCurveTo( + left + width * (50 + curve_cdx) / 100, top + height * curve_cdy / 100, + left + width * 100 / 100, top + height * 100 / 100, + left + width * 0.5, top + height + ); + ctx.bezierCurveTo( + left + width * 0 / 100, top + height * 100 / 100, + left + width * (50 - curve_cdx) / 100, top + height * curve_cdy / 100, + left + width * curve_start_x, top + height * curve_height + ); + ctx.quadraticCurveTo( + left + width * 0.5, top + height * 13 / 100, + left + width * 0.5, top + ); + ctx.closePath(); + if (fill) { + ctx.fill(); + } + if (stroke) { + ctx.stroke(); + } + } + +} + +export default Tear_class; diff --git a/frontend/src/js/tools/shapes/trapezoid.js b/frontend/src/js/tools/shapes/trapezoid.js new file mode 100644 index 0000000..cbb04e8 --- /dev/null +++ b/frontend/src/js/tools/shapes/trapezoid.js @@ -0,0 +1,75 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Trapezoid_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'trapezoid'; + this.layer = {}; + this.best_ratio = 2; + this.snap_line_info = {x: null, y: null}; + this.coords = [ + [20, 0], + [80, 0], + [100, 100], + [0, 100], + [20, 0], + ]; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + this.draw_shape(ctx, x, y, width, height, this.coords); + } + + render(ctx, layer) { + var params = layer.params; + var fill = params.fill; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height, this.coords, false); + + ctx.restore(); + } + +} + +export default Trapezoid_class; diff --git a/frontend/src/js/tools/shapes/triangle.js b/frontend/src/js/tools/shapes/triangle.js new file mode 100644 index 0000000..deef4ca --- /dev/null +++ b/frontend/src/js/tools/shapes/triangle.js @@ -0,0 +1,74 @@ +import app from './../../app.js'; +import config from './../../config.js'; +import Base_tools_class from './../../core/base-tools.js'; +import Base_layers_class from './../../core/base-layers.js'; + +class Triangle_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.ctx = ctx; + this.name = 'triangle'; + this.layer = {}; + this.best_ratio = 2 / Math.sqrt(3); + this.snap_line_info = {x: null, y: null}; + this.coords = [ + [50, 0], + [100, 100], + [0, 100], + [50, 0], + ]; + } + + load() { + this.default_events(); + } + + mousedown(e) { + this.shape_mousedown(e); + } + + mousemove(e) { + this.shape_mousemove(e); + } + + mouseup(e) { + this.shape_mouseup(e); + } + + render_overlay(ctx){ + var ctx = this.Base_layers.ctx; + this.render_overlay_parent(ctx); + } + + demo(ctx, x, y, width, height) { + this.draw_shape(ctx, x, y, width, height, this.coords); + } + + render(ctx, layer) { + var params = layer.params; + var fill = params.fill; + + ctx.save(); + + //set styles + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = 'transparent'; + if(params.border) + ctx.strokeStyle = params.border_color; + if(params.fill) + ctx.fillStyle = params.fill_color; + ctx.lineWidth = params.border_size; + + //draw with rotation support + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(layer.rotate * Math.PI / 180); + this.draw_shape(ctx, -layer.width / 2, -layer.height / 2, layer.width, layer.height, this.coords, false); + + ctx.restore(); + } + +} + +export default Triangle_class; diff --git a/frontend/src/js/tools/sharpen.js b/frontend/src/js/tools/sharpen.js new file mode 100644 index 0000000..ad21137 --- /dev/null +++ b/frontend/src/js/tools/sharpen.js @@ -0,0 +1,139 @@ +import app from './../app.js'; +import config from './../config.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_layers_class from './../core/base-layers.js'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; +import ImageFilters from './../libs/imagefilters.js'; +import Helper_class from './../libs/helpers.js'; + +class Sharpen_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.Helper = new Helper_class(); + this.ctx = ctx; + this.name = 'sharpen'; + this.tmpCanvas = null; + this.tmpCanvasCtx = null; + this.started = false; + } + + load() { + this.default_events(); + } + + default_dragMove(event) { + if (config.TOOL.name != this.name) + return; + this.mousemove(event); + + //mouse cursor + var mouse = this.get_mouse_info(event); + var params = this.getParams(); + this.show_mouse_cursor(mouse.x, mouse.y, params.size, 'circle'); + } + + mousedown(e) { + this.started = false; + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.click_valid == false) { + return; + } + if (config.layer.type != 'image') { + alertify.error('This layer must contain an image. Please convert it to raster to apply this tool.'); + return; + } + if (config.layer.rotate || 0 > 0) { + alertify.error('Erase on rotate object is disabled. Please rasterize first.'); + return; + } + this.started = true; + + //get canvas from layer + this.tmpCanvas = document.createElement('canvas'); + this.tmpCanvasCtx = this.tmpCanvas.getContext("2d"); + this.tmpCanvas.width = config.layer.width_original; + this.tmpCanvas.height = config.layer.height_original; + this.tmpCanvasCtx.drawImage(config.layer.link, 0, 0); + + //do sharpen + this.sharpen_general('click', mouse, params.size); + + //register tmp canvas for faster redraw + config.layer.link_canvas = this.tmpCanvas; + config.need_render = true; + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + var params = this.getParams(); + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + if (this.started == false) { + return; + } + + //do sharpen + this.sharpen_general('move', mouse, params.size); + + //draw draft preview + config.need_render = true; + } + + mouseup(e) { + if (this.started == false) { + return; + } + delete config.layer.link_canvas; + + app.State.do_action( + new app.Actions.Bundle_action('sharpen_tool', 'Sharpen Tool', [ + new app.Actions.Update_layer_image_action(this.tmpCanvas) + ]) + ); + + //decrease memory + this.tmpCanvas.width = 1; + this.tmpCanvas.height = 1; + this.tmpCanvas = null; + this.tmpCanvasCtx = null; + } + + sharpen_general(type, mouse, size) { + var ctx = this.tmpCanvasCtx; + var mouse_x = Math.round(mouse.x) - config.layer.x; + var mouse_y = Math.round(mouse.y) - config.layer.y; + + //adapt to origin size + mouse_x = this.adaptSize(mouse_x, 'width'); + mouse_y = this.adaptSize(mouse_y, 'height'); + var size_w = this.adaptSize(size, 'width'); + var size_h = this.adaptSize(size, 'height'); + + //find center + var center_x = mouse_x - Math.round(size_w / 2); + var center_y = mouse_y - Math.round(size_h / 2); + + //convert float coords to integers + mouse_x = Math.round(mouse_x); + mouse_y = Math.round(mouse_y); + center_x = Math.round(center_x); + center_y = Math.round(center_y); + + var power = 0.5; + if (type == 'move') { + power = power / 10; + } + + var imageData = ctx.getImageData(center_x, center_y, size_w, size_h); + var filtered = ImageFilters.Sharpen(imageData, power); //add effect + this.Helper.image_round(this.tmpCanvasCtx, mouse_x, mouse_y, size_w, size_h, filtered); + } + +} +export default Sharpen_class; diff --git a/frontend/src/js/tools/text.js b/frontend/src/js/tools/text.js new file mode 100644 index 0000000..2c2b0aa --- /dev/null +++ b/frontend/src/js/tools/text.js @@ -0,0 +1,2730 @@ +import app from './../app.js'; +import config from './../config.js'; +import zoomView from './../libs/zoomView.js'; +import Base_tools_class from './../core/base-tools.js'; +import Base_selection_class from './../core/base-selection.js'; +import Base_layers_class from './../core/base-layers.js'; +import GUI_tools_class from './../core/gui/gui-tools.js'; +import Helper_class from './../libs/helpers.js'; +import Dialog_class from './../libs/popup.js'; +import WebFont from 'webfontloader'; +import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js'; + +/** + * TODO + * - Add leading, superscript, subscript + * - Implement text direction (right to left, top to bottom, etc.); currently partial implementation + * - Allow search & add google fonts + * - Undo history + */ + +// Default text styling +// WARNING - changing this could break backwards compatibility! +// Defaults aren't saved in text layer in order to reduce data size and increase meta comparison performance. +export const metaDefaults = { + size: 40, + family: 'Arial', + kerning: 0, + leading: 0, + bold: false, + italic: false, + underline: false, + strikethrough: false, + fill_color: '#008800', + stroke_size: 0, + stroke_color: '#000000' +}; + +// Global map of font name to font metrics information. +const fontMetricsMap = new Map(); +const layerEditors = new WeakMap(); +const fontLoadPromiseMap = new Map(); +const fontLoadMap = new Map(); +fontLoadMap.set('Arial', true); +fontLoadMap.set('Courier', true); +fontLoadMap.set('Impact', true); +fontLoadMap.set('Helvetica', true); +fontLoadMap.set('Monospace', true); +fontLoadMap.set('Tahoma', true); +fontLoadMap.set('Times New Roman', true); +fontLoadMap.set('Verdana', true); + +function load_font_family({ family, variants }, successCallback) { + if (fontLoadMap.get(family) == null) { + fontLoadMap.set(family, false); + const loadPromise = new Promise((resolve, reject) => { + WebFont.load({ + google: { + families: [family + (variants ? ':' + variants.join(',') : '')] + }, + fontactive: (family) => { + fontLoadMap.set(family, true); + fontLoadPromiseMap.delete(family); + resolve(); + }, + fontinactive: (family) => { + alertify.error('Font ' + family + ' could not be loaded.'); + fontLoadPromiseMap.delete(family); + reject(); + } + }); + }); + fontLoadPromiseMap.set(family, loadPromise); + } + if (successCallback) { + const loadPromise = fontLoadPromiseMap.get(family); + if (loadPromise) { + loadPromise.then(successCallback); + } else if (fontLoadMap.get(family) == true) { + requestAnimationFrame(() => { + successCallback(); + }); + } + } +} + +/** + * The canvas's native font metrics implementation doesn't really give us enough information... + */ +const kerningTestCanvas = document.createElement('canvas'); +kerningTestCanvas.width = 10; +kerningTestCanvas.height = 10; +kerningTestCanvas.style = 'font-kerning: normal; text-rendering: optimizeLegibility;'; +const kerningTestCtx = kerningTestCanvas.getContext('2d'); +class Font_metrics_class { + constructor(family, size) { + this.family = family || (family = "Arial"); + this.size = parseInt(size) || (size = 12); + this.kerningMap = new Map(); + + // Preparing container + const line = document.createElement('div'); + const body = document.body; + line.style.position = 'absolute'; + line.style.whiteSpace = 'nowrap'; + line.style.font = size + 'px ' + family; + body.appendChild(line); + + // Now we can measure width and height of the letter + const text = '——————————'; // 10 symbols to be more accurate with width + line.innerHTML = text; + this.width = line.offsetWidth / text.length; + this.height = line.offsetHeight; + + // Now creating 1px sized item that will be aligned to baseline + // to calculate baseline shift + const baseline = document.createElement('span'); + baseline.style.display = 'inline-block'; + baseline.style.overflow = 'hidden'; + baseline.style.width = '1px'; + baseline.style.height = '1px'; + line.appendChild(baseline); + + // Baseline is important for positioning text on canvas + this.baseline = baseline.offsetTop + baseline.offsetHeight; + + document.body.removeChild(line); + } + + /** + * Attempts to determine the height of a letter via pixel comparison + * @param {string} letter - The letter to check + * @param {string} [baseline] - Baseline position override + */ + calculate_letter_bounds(letter, baseline) { + baseline = baseline || 'alphabetic' + kerningTestCanvas.width = this.width; + kerningTestCanvas.height = this.height; + kerningTestCtx.clearRect(0, 0, this.width, this.height); + kerningTestCtx.font = + ' ' + (this.size) + 'px' + + ' ' + this.family; + kerningTestCtx.textAlign = 'left'; + kerningTestCtx.textBaseline = baseline; + kerningTestCtx.fillStyle = '#000000'; + kerningTestCtx.fillText(letter, 0, baseline === 'alphabetic' ? this.baseline : 0); + const pixels = kerningTestCtx.getImageData(0, 0, this.width, this.height).data; + const pixelLength = pixels.length; + let start = 0; + let end = this.height; + for (let i = 0; i < pixelLength; i += 4) { + if (pixels[i + 3] !== 0) { + start = Math.floor(i / 4 / this.width); + break; + } + } + for (let i = pixelLength - 4; i >= 0; i -= 4) { + if (pixels[i + 3] !== 0) { + end = Math.floor(i / 4 / this.width); + break; + } + } + kerningTestCanvas.width = 10; + kerningTestCanvas.height = 10; + return { + top: start, + bottom: end, + height: end - start + } + } + + /** + * Calculate the kerning offset between two letters. + * @param {string} letters - a two character string of the two letters to determine font kerning from. Returns the kerning offset that should be used to draw the 2nd letter. + * @param {object} flags - font style, such as bold or italic + */ + get_kerning_offset(letters, flags = {}) { + let offset = this.kerningMap.get(letters); + if (offset == null) { + kerningTestCtx.font = + ' ' + (flags.italic ? 'italic' : '') + + ' ' + (flags.bold ? 'bold' : '') + + ' ' + (this.size) + 'px' + + ' ' + this.family; + offset = kerningTestCtx.measureText(letters).width - (kerningTestCtx.measureText(letters[0]).width + kerningTestCtx.measureText(letters[1]).width); + this.kerningMap.set(letters, offset); + } + return offset; + } +} + +/** + * This class's job is to store and modify the internal JSON format of a text layer. + */ +class Text_document_class { + constructor() { + this.lines = []; + this.on_change = null; + + // If user edits params while no selection, queue meta insertion for next type. + this.queuedMetaChanges = null; + } + + /** + * Returns the number of lines in the document. + */ + get_line_count() { + return this.lines.length; + } + + /** + * Returns the length of a given line + * @param {number} lineNumber - The number of the line to get the length of + */ + get_line_character_count(lineNumber) { + return this.get_line_text(lineNumber).length; + } + + /** + * Returns the text string at a given line (ignores formatting). + * @param {number} lineNumber - The number of the line to get the text from + */ + get_line_text(lineNumber) { + let lineText = ''; + for (let i = 0; i < this.lines[lineNumber].length; i++) { + lineText += this.lines[lineNumber][i].text; + } + return lineText; + } + + /** + * Returns the position of the end of the the word at the line/character provided + * @param {number} line - The reference line number (0 indexed) + * @param {number} character - The reference character position (0 indexed) + * @param {boolean} noJump - Dont jump to the next word if at the end of current one + */ + get_word_end_position(line, character, noJump) { + let newLine = line; + let newCharacter = character; + let fullText = this.get_line_text(newLine); + if (character === fullText.length && newLine < this.lines.length - 1) { + if (noJump) { + return { line, character }; + } + newLine += 1; + character = 0; + fullText = this.get_line_text(newLine); + } + const text = fullText.slice(character); + if (noJump && text[0] === ' ') { + return { line, character }; + } + for (let i = 1; i < text.length; i++) { + if (text[i] === ' ') { + newCharacter = character + i; + break; + } + } + if (newCharacter === character) { + newCharacter = fullText.length + 1; + } + return { + line: newLine, + character: newCharacter + } + } + + /** + * Returns the position of the start of the the word at the line/character provided + * @param {number} line - The reference line number (0 indexed) + * @param {number} character - The reference character position (0 indexed) + * @param {boolean} noJump - Dont jump to the next word if at the end of current one + */ + get_word_start_position(line, character, noJump) { + let newLine = line; + let newCharacter = character; + let isWrap = false; + if (character === 0 && newLine > 0) { + if (noJump) { + return { line, character }; + } + isWrap = true; + newLine -= 1; + } + const fullText = this.get_line_text(newLine); + if (isWrap) { + character = fullText.length; + } + const text = fullText.slice(0, character); + if (noJump && text[text.length - 1] === ' ') { + return { line, character }; + } + for (let i = -1; i >= -text.length; i--) { + if (text[i + text.length - 1] === ' ') { + newCharacter = character + i; + break; + } + } + if (newCharacter === character) { + newCharacter = 0; + } + return { + line: newLine, + character: newCharacter + } + } + + /** + * Determine if the metadata (formatting) of two text spans is the same, usually used to determine if the spans can be merged together. + */ + is_same_span_meta(meta1, meta2) { + const meta1Keys = Object.keys(meta1).sort(); + const meta2Keys = Object.keys(meta2).sort(); + if (meta1Keys.length !== meta2Keys.length) { + return false; + } + for (let i = 0; i < meta1Keys.length; i++) { + if (meta1Keys[i] !== meta2Keys[i]) { + return false; + } + const meta1Value = meta1[meta1Keys[i]]; + const meta2Value = meta2[meta2Keys[i]]; + if (JSON.stringify(meta1Value) !== JSON.stringify(meta2Value)) { + return false; + } + } + return true; + } + + /** + * Inserts a span with empty text in the document at the specified line and character position + * @param {number} line - The line number to insert at (0 indexed) + * @param {number} character - The character position to insert at (0 indexed) + * @param {object} meta - Metadata to associate with span + */ + insert_empty_span(line, character, meta) { + let insertedSpan = null; + const lineDef = this.lines[line]; + let newLine = []; + let spanStartCharacter = 0; + let wasInserted = false; + for (let span of lineDef) { + if (!wasInserted && character >= spanStartCharacter && character <= spanStartCharacter + span.text.length) { + let textBefore = span.text.slice(0, character - spanStartCharacter); + let textAfter = span.text.slice(character - spanStartCharacter); + if (textBefore.length > 0) { + newLine.push({ + text: textBefore, + meta: JSON.parse(JSON.stringify(span.meta)) + }); + } + const newMeta = JSON.parse(JSON.stringify(span.meta)); + for (let metaKey in meta) { + newMeta[metaKey] = meta[metaKey]; + } + insertedSpan = { + text: '', + meta: newMeta + }; + newLine.push(insertedSpan); + if (textAfter.length > 0) { + newLine.push({ + text: textAfter, + meta: JSON.parse(JSON.stringify(span.meta)) + }); + } + wasInserted = true; + } else { + newLine.push(span); + } + spanStartCharacter += span.text.length; + } + this.lines[line] = newLine; + return insertedSpan; + } + + /** + * Inserts a text string in the document at the specified line and character position + * @param {string} text - The text string to insert + * @param {number} line - The line number to insert at (0 indexed) + * @param {number} character - The character position to insert at (0 indexed) + */ + insert_text(text, line, character) { + + let insertedSpan; + if (this.queuedMetaChanges) { + insertedSpan = this.insert_empty_span(line, character, this.queuedMetaChanges); + this.queuedMetaChanges = null; + } + + const insertLine = this.lines[line]; + const textHasNewline = text.includes('\n'); + let characterCount = 0; + let modifyingSpan = null; + let previousSpans = []; + let nextSpans = []; + let newLine = line; + let newCharacter = character; + + // Insert text into span at specified line/character + for (let i = 0; i < insertLine.length; i++) { + const span = insertLine[i]; + const spanLength = span.text.length; + if (!modifyingSpan && (character > characterCount || character === 0) && character <= characterCount + spanLength) { + if (insertLine[i + 1] && insertLine[i + 1].text === '') { + modifyingSpan = insertLine[i + 1]; + } else { + modifyingSpan = span; + } + const textIdx = character - characterCount; + modifyingSpan.text = modifyingSpan.text.slice(0, textIdx) + text + modifyingSpan.text.slice(textIdx); + if (!textHasNewline) { + newCharacter = characterCount + textIdx + text.length; + break; + } + } else if (textHasNewline) { + if (modifyingSpan) { + nextSpans.push(span); + } else { + previousSpans.push(span); + } + } + characterCount += spanLength; + } + + // Create new lines if newline character was used + if (textHasNewline && modifyingSpan) { + const modifiedSpans = []; + const textLines = modifyingSpan.text.split('\n'); + for (let i = 0; i < textLines.length; i++) { + modifiedSpans.push({ + meta: JSON.parse(JSON.stringify(modifyingSpan.meta)), + text: textLines[i] + }); + } + this.lines[line] = [...previousSpans, modifiedSpans.shift()]; + for (let i = 0; i < modifiedSpans.length; i++) { + if (i === modifiedSpans.length - 1) { + if (!modifiedSpans[i].text && nextSpans.length > 0) { + this.lines.splice(line + i + 1, 0, nextSpans); + } else { + this.lines.splice(line + i + 1, 0, [modifiedSpans[i], ...nextSpans]); + } + newLine = line + i + 1; + newCharacter = text.length - 1 - text.lastIndexOf('\n'); + } else { + this.lines.splice(line + i + 1, 0, [modifiedSpans[i]]); + } + } + } + + // Notify change + if (this.on_change) { + this.on_change(this.lines); + } + + // Return end position + return { + line: newLine, + character: newCharacter + }; + } + + /** + * Deletes text withing the specified range + * @param {number} startLine - The starting line of the text range + * @param {number} startCharacter - The character position at the starting line of the text range + * @param {number} endLine - The ending line of the text range + * @param {number} endCharacter - The character position at the ending line of the text range + */ + delete_range(startLine, startCharacter, endLine, endCharacter) { + // Check bounds + startLine >= 0 || (startLine = 0); + startCharacter >= 0 || (startCharacter = 0); + endLine < this.lines.length || (endLine = this.lines.length - 1); + const endLineCharacterCount = this.get_line_character_count(endLine); + endCharacter <= endLineCharacterCount || ( + endCharacter = endLineCharacterCount + ); + + // Early return if there's nothing to delete + if (startLine === endLine && startCharacter === endCharacter) { + return { + line: startLine, + character: startCharacter + }; + } + + // Get spans in start line before range + const beforeSpans = []; + const afterSpans = []; + let characterCount = 0; + let startSpan = null; + let startSpanDeleteIndex = 0; + for (let i = 0; i < this.lines[startLine].length; i++) { + const span = this.lines[startLine][i]; + const spanLength = span.text.length; + if (!startSpan && (startCharacter > characterCount || startCharacter === 0) && startCharacter <= characterCount + spanLength) { + startSpan = span; + startSpanDeleteIndex = Math.max(0, startCharacter - characterCount); + break; + } + if (!startSpan) { + beforeSpans.push(span); + } + characterCount += spanLength; + } + + // Get spans in end line after range + characterCount = 0; + let endSpan = null; + let endSpanDeleteIndex = 0; + for (let i = 0; i < this.lines[endLine].length; i++) { + const span = this.lines[endLine][i]; + const spanLength = span.text.length; + if (!endSpan && (endCharacter > characterCount || endCharacter === 0) && endCharacter <= characterCount + spanLength) { + endSpan = span; + endSpanDeleteIndex = Math.max(0, endCharacter - characterCount); + } + else if (endSpan) { + afterSpans.push(span); + } + characterCount += spanLength; + } + + // Merge start and end lines + this.lines[startLine] = [...beforeSpans]; + if (startSpan === endSpan || this.is_same_span_meta(startSpan.meta, endSpan.meta)) { + const combinedSpans = { + meta: startSpan.meta, + text: startSpan.text.slice(0, startSpanDeleteIndex) + endSpan.text.slice(endSpanDeleteIndex) + }; + if (combinedSpans.text || (beforeSpans.length === 0 && afterSpans.length === 0)) { + this.lines[startLine].push(combinedSpans); + } + } else { + const middleSpans = []; + let isAddedStartSpan = false; + let isAddedEndSpan = false; + if (startSpan) { + startSpan.text = startSpan.text.slice(0, startSpanDeleteIndex); + if (startSpan.text) { + middleSpans.push(startSpan); + isAddedStartSpan = true; + } + } + if (endSpan) { + endSpan.text = endSpan.text.slice(endSpanDeleteIndex) + if (endSpan.text || middleSpans.length === 0) { + middleSpans.push(endSpan); + isAddedEndSpan = true; + } + } + if (isAddedStartSpan && !isAddedEndSpan) { + const afterSpan = afterSpans[0]; + if (afterSpan && this.is_same_span_meta(startSpan.meta, afterSpan.meta)) { + afterSpans.shift(); + startSpan.text += afterSpan.text; + } + } + else if (isAddedEndSpan && !isAddedStartSpan) { + const beforeSpan = beforeSpans[beforeSpans.length - 1]; + if (beforeSpan && this.is_same_span_meta(beforeSpan.meta, endSpan.meta)) { + beforeSpans.pop(); + beforeSpan.text += endSpan.text; + } + } + else if (middleSpans.length === 0) { + const beforeSpan = beforeSpans[beforeSpans.length - 1]; + const afterSpan = afterSpans[0]; + if (beforeSpan && afterSpan && this.is_same_span_meta(beforeSpan.meta, afterSpan.meta)) { + afterSpans.shift(); + beforeSpan.text += afterSpan.text; + } + } + this.lines[startLine] = this.lines[startLine].concat(middleSpans); + } + this.lines[startLine] = this.lines[startLine].concat(afterSpans); + + // Delete lines in-between range + this.lines.splice(startLine + 1, endLine - startLine); + + // Notify change + if (this.on_change) { + this.on_change(this.lines); + } + + // Return new position + return { + line: startLine, + character: startCharacter + }; + } + + /** + * Deletes a single character in front or behind the specified character position, handling deleting new lines, etc. + * @param {boolean} forward - True if deleting the next character, otherwise deletes the previous character + * @param {number} startLine - The line number to delete from + * @param {number} startCharacter - The character position to delete from + */ + delete_character(forward, startLine, startCharacter) { + let endLine = startLine; + let endCharacter = startCharacter; + + // Delete forwards + if (forward) { + // If there are characters after cursor on this line we remove one + if (startCharacter < this.get_line_character_count(startLine)) { + ++endCharacter; + } + // if there are Lines after this one we append it + else if (startLine < this.lines.length - 1) { + ++endLine; + endCharacter = 0; + } + } + // Delete backwards + else { + // If there are characters before the cursor on this line we remove one + if (startCharacter > 0) { + --startCharacter; + } + // if there are rows before we append current to previous one + else if (startLine > 0) { + --startLine; + startCharacter = this.get_line_character_count(startLine); + } + } + + return this.delete_range(startLine, startCharacter, endLine, endCharacter); + } + + /** + * Retrieves a metadata summary object for the specified range of text. + * @param {number} startLine - The starting line of the text range + * @param {number} startCharacter - The character position at the starting line of the text range + * @param {number} endLine - The ending line of the text range + * @param {number} endCharacter - The character position at the ending line of the text range + */ + get_meta_range(startLine, startCharacter, endLine, endCharacter) { + // Check bounds + startLine >= 0 || (startLine = 0); + startCharacter >= 0 || (startCharacter = 0); + endLine < this.lines.length || (endLine = this.lines.length - 1); + const endLineCharacterCount = this.get_line_character_count(endLine); + endCharacter <= endLineCharacterCount || ( + endCharacter = endLineCharacterCount + ); + const isEmpty = startLine === endLine && startCharacter === endCharacter; + + // Loop through all spans in range and collect meta values + const metaCollection = {}; + for (const metaKey in metaDefaults) { + metaCollection[metaKey] = []; + } + let isInsideRange = false; + for (let lineIndex = startLine; lineIndex <= endLine; lineIndex++) { + const line = this.lines[lineIndex]; + let spanStartCharacter = 0; + let startSpan = null; + let endSpan = null; + for (let spanIndex = 0; spanIndex < line.length; spanIndex++) { + const span = line[spanIndex]; + if (lineIndex === startLine) { + if ( + (!isEmpty && startCharacter >= spanStartCharacter && startCharacter < spanStartCharacter + span.text.length) || + (isEmpty && startCharacter > spanStartCharacter && startCharacter <= spanStartCharacter + span.text.length) || + (startCharacter === 0 && spanStartCharacter === 0) + ) { + isInsideRange = true; + startSpan = span; + } + } + if (lineIndex === endLine && isInsideRange) { + if ( + (!isEmpty && endCharacter <= spanStartCharacter + span.text.length) || + (isEmpty && endCharacter < spanStartCharacter + span.text.length) + ) { + endSpan = span; + isInsideRange = false; + } + } + if (isInsideRange || startSpan === span || (!isEmpty && endSpan === span)) { + for (const metaKey in metaCollection) { + let metaValue = span.meta[metaKey]; + if (metaValue == null) { + metaValue = metaDefaults[metaKey]; + } + if (!metaCollection[metaKey].includes(metaValue)) { + metaCollection[metaKey].push(metaValue); + } + } + } + spanStartCharacter += span.text.length; + } + } + + // Fill in default values for undefined meta keys + for (const metaKey in metaDefaults) { + if (metaCollection[metaKey].length === 0) { + metaCollection[metaKey] = [metaDefaults[metaKey]]; + } + } + return metaCollection; + } + + /** + * Sets styling metadata for the specified range of text. + * @param {number} startLine - The starting line of the text range + * @param {number} startCharacter - The character position at the starting line of the text range + * @param {number} endLine - The ending line of the text range + * @param {number} endCharacter - The character position at the ending line of the text range + * @param {object} meta - The meta to set + */ + set_meta_range(startLine, startCharacter, endLine, endCharacter, meta) { + // Check bounds + startLine >= 0 || (startLine = 0); + startCharacter >= 0 || (startCharacter = 0); + endLine < this.lines.length || (endLine = this.lines.length - 1); + const endLineCharacterCount = this.get_line_character_count(endLine); + endCharacter <= endLineCharacterCount || ( + endCharacter = endLineCharacterCount + ); + + // Set meta of spans in selection + let isInsideRange = false; + for (let lineIndex = startLine; lineIndex <= endLine; lineIndex++) { + const line = this.lines[lineIndex]; + let newLine = []; + let spanStartCharacter = 0; + for (let span of line) { + const spanText = span.text; + const spanLength = spanText.length; + if (lineIndex === startLine) { + if (startCharacter <= spanStartCharacter) { + isInsideRange = true; + } + } + if (lineIndex === endLine) { + if (endCharacter < spanStartCharacter + spanLength) { + isInsideRange = false; + } + } + // Selection start splits the span it's inside of + let choppedStartCharacters = 0; + if (startCharacter > spanStartCharacter && startCharacter < spanStartCharacter + spanLength && lineIndex === startLine) { + choppedStartCharacters = startCharacter - spanStartCharacter; + newLine.push({ + text: span.text.slice(0, startCharacter - spanStartCharacter), + meta: JSON.parse(JSON.stringify(span.meta)) + }); + span.text = span.text.slice(startCharacter - spanStartCharacter); + isInsideRange = true; + } + newLine.push(span); + // Selection end splits the span it's inside of + if (endCharacter > spanStartCharacter && endCharacter < spanStartCharacter + spanLength && lineIndex === endLine) { + newLine.push({ + text: span.text.slice(endCharacter - spanStartCharacter - choppedStartCharacters), + meta: JSON.parse(JSON.stringify(span.meta)) + }); + span.text = span.text.slice(0, endCharacter - spanStartCharacter - choppedStartCharacters); + isInsideRange = true; + } + // Add meta to span + if (isInsideRange) { + for (const metaKey in meta) { + span.meta[metaKey] = meta[metaKey]; + } + } + spanStartCharacter += spanLength; + } + this.lines[lineIndex] = newLine; + } + + this.normalize(startLine, endLine); + + // Notify change + if (this.on_change) { + this.on_change(this.lines); + } + } + + /** + * Merges sibling spans that have the same metadata, and removes empty spans. + * @param {number} startLine - The starting line of the text range + * @param {number} endLine - The ending line of the text range + */ + normalize(startLine, endLine) { + for (let lineIndex = startLine; lineIndex <= endLine; lineIndex++) { + const line = this.lines[lineIndex]; + let spanIndex = 0; + for (spanIndex = 0; spanIndex < line.length; spanIndex++) { + const span1 = line[spanIndex]; + const span2 = line[spanIndex + 1]; + if (span1 && span2 && this.is_same_span_meta(span1.meta, span2.meta)) { + line[spanIndex] = { + text: span1.text + span2.text, + meta: span1.meta + }; + line.splice(spanIndex + 1, 1); + spanIndex--; + continue; + } + if (span1.text === '' && line.length > 1) { + line.splice(spanIndex, 1); + spanIndex--; + continue; + } + } + } + } + +} + + +/** + * This class represents a single selection range in a text editor's document. + */ +class Text_selection_class { + constructor(/* Text_editor_class */ editor) { + this.editor = editor; + this.isVisible = false; + this.isCursorVisible = false; + this.isActiveSideEnd = true; + this.isBlinkVisible = true; + this.blinkInterval = 500; + + this.start = { + line: 0, + character: 0 + }; + + this.end = { + line: 0, + character: 0 + }; + + this.set_position(0, 0); + } + + /** + * Returns if the current text selection contains no characters + * @returns {boolean} + */ + is_empty() { + return this.compare_position(this.start.line, this.start.character, this.end.line, this.end.character) === 0; + } + + /** + * Determines the relative position of two line/character sets. + * @param {number} line1 + * @param {number} character1 + * @param {number} line2 + * @param {number} character2 + * @returns {number} -1 if line1/character1 is less than line2/character2, 1 if greater, and 0 if equal + */ + compare_position(line1, character1, line2, character2) { + if (line1 < line2) { + return -1; + } else if (line1 > line2) { + return 1; + } else { + if (character1 < character2) { + return -1; + } else if (character1 > character2) { + return 1; + } else { + return 0; + } + } + } + + /** + * Sets the head position of the selection to the specified line/character, optionally extends to selection to that position. + * @param {number} line - The line number to set the selection to + * @param {number} character - The character index to set the selection to + * @param {boolean} [keepSelection] - If true, extends the current selection to the specified position. If false or undefined, sets an empty selection at that position. + */ + set_position(line, character, keepSelection) { + if (line == null) { + line = this.end.line; + } + if (character == null) { + character = this.end.character; + } + + // Check lower bounds + line >= 0 || (line = 0); + character >= 0 || (character = 0); + + // Check upper bounds + const lineCount = this.editor.document.get_line_count(); + line < lineCount || (line = lineCount - 1); + const lineCharacterCount = this.editor.document.get_line_character_count(line); + character <= lineCharacterCount || (character = lineCharacterCount); + + // Add to selection + if (keepSelection) { + const positionCompare = this.compare_position( + line, + character, + this.start.line, + this.start.character + ); + + // Determine whether we should make the start side of the range active, selection moving left or up. + if (positionCompare === -1 && (this.is_empty() || line < this.start.line)) { + this.isActiveSideEnd = false; + } + + // Assign new value to the side that is active + if (this.isActiveSideEnd) { + this.end.line = line; + this.end.character = character; + } else { + this.start.line = line; + this.start.character = character; + } + + // Making sure that end is greater than start and swap if necessary + if (this.compare_position(this.start.line, this.start.character, this.end.line, this.end.character) > 0) { + this.isActiveSideEnd = !this.isActiveSideEnd; + const temp = { + line: this.start.line, + character: this.start.character + } + this.start.line = this.end.line; + this.start.character = this.end.character; + this.end.line = temp.line; + this.end.character = temp.character; + } + } + // Empty cursor move + else { + this.isActiveSideEnd = true; + this.start.line = this.end.line = line; + this.start.character = this.end.character = character; + } + + // Reset cursor blink + this.isBlinkVisible = true; + if (this.isVisible) { + this.start_blinking(); + } + } + + /** + * Retrieves the position of the head of the selection (could be the start or end of the selection based on previous operations) + * @returns {object} - { line, character } + */ + get_position() { + if (this.isActiveSideEnd) { + return { + character: this.end.character, + line: this.end.line + }; + } else { + return { + character: this.start.character, + line: this.start.line + }; + } + } + + /** + * Gets the plain text value in the current selection range. + * @returns {string} + */ + get_text() { + const positionCompare = this.compare_position(this.start.line, this.start.character, this.end.line, this.end.character); + const firstLine = positionCompare === 1 ? this.end.line : this.start.line; + const lastLine = positionCompare === 1 ? this.start.line : this.end.line; + const firstCharacter = positionCompare === 1 ? this.end.character : this.start.character; + const lastCharacter = positionCompare === 1 ? this.start.character : this.end.character; + let textLines = []; + for (let i = firstLine; i <= lastLine; i++) { + if (i === firstLine && i === lastLine) { + textLines.push(this.editor.document.get_line_text(i).slice(firstCharacter, lastCharacter)); + } else if (i === firstLine) { + textLines.push(this.editor.document.get_line_text(i).slice(firstCharacter)); + } else if (i === lastLine) { + textLines.push(this.editor.document.get_line_text(i).slice(0, lastCharacter)); + } else { + textLines.push(this.editor.document.get_line_text(i)); + } + } + return textLines.join('\n'); + } + + /** + * Sets the visibility of the selection in the editor. + * @param {boolean} isVisible + */ + set_visible(isVisible) { + if (this.isVisible != isVisible) { + this.isVisible = isVisible; + } + } + + /** + * Sets the visibility of the selection cursor in the editor. + * @param {boolean} isVisible + */ + set_cursor_visible(isVisible) { + if (this.isCursorVisible != isVisible) { + this.isCursorVisible = isVisible; + if (this.isCursorVisible) { + this.isBlinkVisible = true; + this.start_blinking(); + } else { + this.stop_blinking(); + } + } + } + + /** + * Starts the selection cursor blinking. + */ + start_blinking() { + clearInterval(this.blinkIntervalHandle); + this.blinkIntervalHandle = setInterval(this.blink.bind(this), this.blinkInterval); + } + + /** + * Stops the selection cursor blinking. + */ + stop_blinking() { + clearInterval(this.blinkIntervalHandle); + } + + /** + * Toggles the visibility of the selection cursor. + */ + blink() { + this.isBlinkVisible = !this.isBlinkVisible; + const firstLine = Math.min(this.start.line, this.end.line); + const lastLine = Math.max(this.start.line, this.end.line); + /* + this.editor.render({ + lineStart: firstLine, + lineEnd: lastLine + }); + */ + // this.Base_layers.render(); + } + + /** + * Moves the cursor to a previous line. + * @param {number} length - The number of lines to move + * @param {boolean} keepSelection - Whether to move to an empty selection or extend the current selection + */ + move_line_previous(length, keepSelection) { + length = length == null ? 1 : length; + const position = this.get_position(); + this.set_position(position.line - length, null, keepSelection); + } + + /** + * Moves the cursor to a next line. + * @param {number} length - The number of lines to move + * @param {boolean} keepSelection - Whether to move to an empty selection or extend the current selection + */ + move_line_next(length, keepSelection) { + length = length == null ? 1 : length; + const position = this.get_position(); + this.set_position(position.line + length, null, keepSelection); + } + + /** + * Moves to the start of the current line. + * @param {boolean} keepSelection - Whether to move to an empty selection or extend the current selection + */ + move_line_start(keepSelection) { + const position = this.get_position(); + this.set_position(position.line, 0, keepSelection); + } + + /** + * Moves to the end of the current line. + * @param {boolean} keepSelection - Whether to move to an empty selection or extend the current selection + */ + move_line_end(keepSelection) { + const position = this.get_position(); + this.set_position(position.line, this.editor.document.get_line_character_count(position.line), keepSelection); + } + + /** + * Moves the cursor to a character behind in the document, handles line wrapping. + * @param {number} length - The number of characters to move + * @param {boolean} keepSelection - Whether to move to an empty selection or extend the current selection + */ + move_character_previous(length, keepSelection) { + length = length == null ? 1 : length; + const position = this.get_position(); + if (position.character - length < 0) { + if (position.line > 0) { + this.set_position(position.line - 1, this.editor.document.get_line_character_count(position.line - 1), keepSelection); + } + } else { + this.set_position(position.line, position.character - length, keepSelection); + } + } + + /** + * Moves the cursor to a character ahead in the document, handles line wrapping. + * @param {number} length - The number of characters to move + * @param {boolean} keepSelection - Whether to move to an empty selection or extend the current selection + */ + move_character_next(length, keepSelection) { + length = length == null ? 1 : length; + const position = this.get_position(); + const characterCount = this.editor.document.get_line_character_count(position.line); + if (position.character + length > characterCount) { + if (position.line + 1 < this.editor.document.lines.length) { + this.set_position(position.line + 1, 0, keepSelection); + } + } else { + this.set_position(position.line, position.character + length, keepSelection); + } + } + + /** + * Moves the cursor to the beginning of the current word or previous word, handles line wrapping. + * @param {boolean} keepSelection - Whether to move to an empty selection or extend the current selection + */ + move_word_previous(keepSelection) { + const position = this.get_position(); + const newPosition = this.editor.document.get_word_start_position(position.line, position.character); + this.set_position(newPosition.line, newPosition.character, keepSelection); + } + + /** + * Moves the cursor to the end of the current word or next word, handles line wrapping. + * @param {boolean} keepSelection - Whether to move to an empty selection or extend the current selection + */ + move_word_next(keepSelection) { + const position = this.get_position(); + const newPosition = this.editor.document.get_word_end_position(position.line, position.character); + this.set_position(newPosition.line, newPosition.character, keepSelection); + } +} + + +/** + * This class handles rendering a text layer and editing it based on keyboard/mouse/touch controls + */ +class Text_editor_class { + constructor(options) { + options = options || {}; + + this.editingCtx = document.getElementById('canvas_minipaint').getContext("2d"); + this.hasValueChanged = false; + + // Text boundary and offsets are precomputed before drawn + this.lineRenderInfo = null; + this.lastCalculatedZoom = 0; + this.lastCalculatedLayerWidth = 0; + this.lastCalculatedLayerHeight = 0; + this.textBoundaryWidth = 0; + this.textBoundaryHeight = 0; + + // Styling options during render + this.selectionBackgroundColor = options.selectionBackgroundColor || '#1C79C4'; + this.selectionTextColor = options.selectionTextColor || '#FFFFFF'; + + // Offset from top/left of layer for cursor visibility + this.drawOffsetTop = options.paddingVertical != null ? options.paddingVertical : 6; + this.drawOffsetLeft = options.paddingHorizontal != null ? options.paddingHorizontal : 10; + + // Tracking internal state for keyboard/mouse/touch control + this.shiftPressed = false; + this.ctrlPressed = false; + this.isMouseSelectionActive = false; + this.mouseSelectionStartX = 0; + this.mouseSelectionStartY = 0; + this.mouseSelectionStartLine = null; + this.mouseSelectionStartCharacter = null; + this.mouseSelectionMoveX = null; + this.mouseSelectionMoveY = null; + this.mouseSelectionEdgeScrollInterval = null; + this.focused = false; + + // Text document for this editor + this.document = new Text_document_class(); + this.document.lines = [[{ text: '', meta: {} }]]; + this.wrappedLines = [[]]; + + // Text selection for this editor + this.selection = new Text_selection_class(this); + + // The layer associated with this editor (so data can be updated) + this.layer = null; + this.document.on_change = () => { + this.layer.data = this.document.lines; + }; + } + + /** + * Sets the lines of the document (from layer data) + * @param {array} lines + */ + set_lines(lines) { + this.document.lines = lines || [[{ text: '', meta: {} }]]; + } + + /** + * Returns the text string at a given line wrap (ignores formatting). + * @param {object} wrap - The wrap definition + */ + get_wrap_text(wrap) { + let wrapText = ''; + for (let i = 0; i < wrap.spans.length; i++) { + wrapText += wrap.spans[i].text; + } + return wrapText; + } + + /** + * Calculates font metrics for the given span and returns it. Caches by default. + * @param {object} span - The span to calculate metrics for + * @param {boolean} noCache - Skip caching if the metrics is expected to change in the future (e.g. font family not loaded yet.) + */ + get_span_font_metrics(span, noCache) { + const fontSize = (span.meta.size || metaDefaults.size); + const fontName = (span.meta.family || metaDefaults.family); + let fontMetrics = fontMetricsMap.get(fontName + '_' + fontSize); + if (!fontMetrics) { + fontMetrics = new Font_metrics_class(fontName, fontSize); + if (!noCache) { + fontMetricsMap.set(fontName + '_' + fontSize, fontMetrics); + } + } + return fontMetrics; + } + + /** + * Returns the complete text of the document. + */ + get_complete_text() { + let completeText = ''; + for (let line of this.document.lines) { + for (let span of line) { + completeText += span.text; + } + if (this.document.lines.indexOf(line) !== this.document.lines.length - 1) { + completeText += '\n'; + } + } + return completeText; + } + + replace_entire_IME_text(beforeTempText, newText) { + const cursorPosition = this.selection.get_position(); + let allText = beforeTempText; + let lines = allText.split('\n'); + let currentLineText = lines[cursorPosition.line]; + let beforeText = currentLineText.substring(0, cursorPosition.character); + let afterText = currentLineText.substring(cursorPosition.character); + let updatedLineText = beforeText + newText + afterText; + lines[cursorPosition.line] = updatedLineText; + + const newLines = lines.map(lineText => { + return [{ text: lineText, meta: {} }]; + }); + this.set_lines(newLines); + this.hasValueChanged = true; + } + + set_IME_position(newText) { + const cursorPosition = this.selection.get_position(); + let newTextLines = newText.split('\n'); + let newCursorLine = cursorPosition.line + newTextLines.length - 1; + let newCursorCharacter = newText.length; + this.selection.set_position(newCursorLine, newCursorCharacter + cursorPosition.character); + this.hasValueChanged = true; + } + + + + insert_text_at_current_position(text) { + if (!this.selection.is_empty()) { + this.delete_character_at_current_position(); + } + const position = this.selection.get_position(); + const newPosition = this.document.insert_text(text, position.line, position.character); + this.selection.set_position(newPosition.line, newPosition.character); + this.hasValueChanged = true; + } + + delete_character_at_current_position(forward) { + let newPosition; + if (this.selection.is_empty()) { + const position = this.selection.get_position(); + newPosition = this.document.delete_character(forward, position.line, position.character); + } else { + newPosition = this.document.delete_range( + this.selection.start.line, + this.selection.start.character, + this.selection.end.line, + this.selection.end.character + ); + } + this.selection.set_position(newPosition.line, newPosition.character); + this.hasValueChanged = true; + } + + delete_selection() { + let newPosition = this.document.delete_range( + this.selection.start.line, + this.selection.start.character, + this.selection.end.line, + this.selection.end.character + ); + this.selection.set_position(newPosition.line, newPosition.character); + this.hasValueChanged = true; + } + + trigger_cursor_start(layer, layerX, layerY) { + this.isMouseSelectionActive = true; + this.mouseSelectionStartX = layerX; + this.mouseSelectionStartY = layerY; + const cursorStart = this.get_cursor_position_from_absolute_position(layer, layerX, layerY); + this.mouseSelectionStartLine = cursorStart.line; + this.mouseSelectionStartCharacter = cursorStart.character; + this.selection.set_position(cursorStart.line, cursorStart.character, false); + } + + trigger_cursor_move(layer, layerX, layerY) { + const isInsideCanvas = true; // layerX > 0 && layerY > 0 && layerX < this.lastCalculatedLayerWidth && layerY < this.lastCalculatedLayerHeight; + if (this.isMouseSelectionActive && isInsideCanvas) { + this.mouseSelectionMoveX = layerX; + this.mouseSelectionMoveY = layerY; + const cursorEnd = this.get_cursor_position_from_absolute_position(layer, layerX, layerY); + this.selection.set_position(this.mouseSelectionStartLine, this.mouseSelectionStartCharacter, false); + this.selection.set_position(cursorEnd.line, cursorEnd.character, true); + } + } + + trigger_cursor_end() { + this.isMouseSelectionActive = false; + this.mouseSelectionMoveX = null; + this.mouseSelectionMoveY = null; + } + + get_cursor_position_from_absolute_position(layer, x, y) { + let line = -1; + let character = -1; + + if (this.lineRenderInfo) { + const textDirection = layer.params.text_direction; + const wrapDirection = layer.params.wrap_direction; + const isHorizontalTextDirection = ['ltr', 'rtl'].includes(textDirection); + const isNegativeTextDirection = ['rtl', 'btt'].includes(textDirection); + + let characterPosition = isHorizontalTextDirection ? x : y; + let wrapPosition = isHorizontalTextDirection ? y : x; + + const wrapSizes = this.lineRenderInfo.wrapSizes; + let wrapRelativeIndex = -1; + + let globalWrapIndex = 0; + for (let [lineIndex, lineInfo] of this.lineRenderInfo.lines.entries()) { + wrapRelativeIndex = 0; + for (let wrap of lineInfo.wraps) { + if (wrapPosition < wrapSizes[globalWrapIndex].offset + wrapSizes[globalWrapIndex].size) { + line = lineIndex; + break; + } + globalWrapIndex++; + wrapRelativeIndex++; + } + if (line > -1) { + break; + } + } + if (line === -1) { + line = this.lineRenderInfo.lines.length - 1; + wrapRelativeIndex = -1; + } + const wraps = this.lineRenderInfo.lines[line].wraps; + if (wrapRelativeIndex === -1) { + wrapRelativeIndex = wraps.length - 1; + } + let previousWrapCharacterCount = 0; + for (let w = 0; w < wrapRelativeIndex; w++) { + previousWrapCharacterCount += this.get_wrap_text(wraps[w]).length; + } + const characterCount = this.get_wrap_text(wraps[wrapRelativeIndex]).length; + const characterOffsets = wraps[wrapRelativeIndex].characterOffsets; + for (let characterNumber = 0; characterNumber < characterCount; characterNumber++) { + const leftPosition = characterOffsets[characterNumber]; + const rightPosition = characterOffsets[characterNumber + 1]; + if (characterPosition <= leftPosition + ((rightPosition - leftPosition) * 0.5)) { + character = previousWrapCharacterCount + characterNumber; + break; + } + if (characterNumber === characterCount - 1 && character === -1) { + character = previousWrapCharacterCount + characterCount; + } + } + if (character === -1) { + character = this.document.get_line_character_count(line); + } + } + return { line, character }; + } + + calculate_text_placement(ctx, layer) { + const boundary = layer.params.boundary; + const textDirection = layer.params.text_direction; + const wrapDirection = layer.params.wrap_direction; + const halign = layer.params.halign; + const valign = layer.params.valign; + const isHorizontalTextDirection = ['ltr', 'rtl'].includes(textDirection); + const isNegativeTextDirection = ['rtl', 'btt'].includes(textDirection); + + let totalTextDirectionSize = 0; + let totalWrapDirectionSize = 0; + let textDirectionMaxSize = isHorizontalTextDirection ? layer.width : layer.height; + + // Determine new lines based on text wrapping, if applicable + let lineRenderInfo = { + wrapSizes: [], + lines: [] + }; + for (let line of this.document.lines) { + let wrapAccumulativeSize = 0; + let wrapCharacterOffsets = [0]; + let lineWraps = []; + let currentWrapSpans = [...line]; + let s = 0; + let fontMetrics = null; + let character = null; + let nextCharacter = null; + let fontKerning = 0; + for (s = 0; s < currentWrapSpans.length; s++) { + const span = currentWrapSpans[s]; + const kerning = span.meta.kerning || metaDefaults.kerning; + const family = span.meta.family || metaDefaults.family; + const size = span.meta.size || metaDefaults.size; + fontMetrics = this.get_span_font_metrics(span, !fontLoadMap.get(family)); + if (isHorizontalTextDirection) { + ctx.font = + ' ' + (span.meta.italic ? 'italic' : '') + + ' ' + (span.meta.bold ? 'bold' : '') + + ' ' + size + 'px' + + ' ' + family; + } + for (let c = 0; c < span.text.length; c++) { + character = span.text[c]; + if (layer.params.kerning === 'metrics') { + nextCharacter = span.text[c + 1]; + if (!nextCharacter && c === span.text.length - 1 && currentWrapSpans[s + 1]) { + const nextSpan = currentWrapSpans[s + 1]; + if (family === (nextSpan.meta.family || metaDefaults.family) && size === (nextSpan.meta.size || metaDefaults.size)) { + nextCharacter = nextSpan.text[0]; + } + } + fontKerning = isHorizontalTextDirection && nextCharacter ? fontMetrics.get_kerning_offset(character + nextCharacter) : 0; + } + const characterSize = isHorizontalTextDirection ? ctx.measureText(character).width : fontMetrics.height; + wrapAccumulativeSize += characterSize + fontKerning + kerning; + if (boundary !== 'dynamic' && wrapAccumulativeSize > textDirectionMaxSize && ![' ', '-'].includes(character)) { + // Find last span with space + let dividerPosition = -1; + let bs = s; + for (; bs >= 0; bs--) { + const backwardsSpan = currentWrapSpans[bs]; + const backwardsSpanText = (bs === s) ? backwardsSpan.text.substring(0, c) : backwardsSpan.text; + dividerPosition = backwardsSpanText.lastIndexOf(' '); + const dashPosition = backwardsSpanText.lastIndexOf('-'); + if (dashPosition > dividerPosition) { + dividerPosition = dashPosition; + } + if (dividerPosition > -1) { + break; + } + } + let beforeSpans = []; + let afterSpans = []; + // Found a previous span on the current line wrap that contains a space, split the line + if (dividerPosition > -1) { + beforeSpans = currentWrapSpans.slice(0, bs); + afterSpans = currentWrapSpans.slice(bs + 1); + const beforeText = currentWrapSpans[bs].text.substring(0, dividerPosition + 1); + const afterText = currentWrapSpans[bs].text.substring(dividerPosition + 1); + if (beforeText.length > 0) { + beforeSpans.push({ + text: beforeText, + meta: currentWrapSpans[bs].meta + }); + } + if (afterText.length > 0) { + afterSpans.unshift({ + text: afterText, + meta: currentWrapSpans[bs].meta + }); + } + } + // For word split only, break out. + else if (layer.params.wrap === 'word') { + wrapCharacterOffsets.push(wrapAccumulativeSize); + break; + } + // Otherwise, split the word + else { + if (s === 0 && c === 0) { + c++; + wrapCharacterOffsets.push(wrapAccumulativeSize); + } + beforeSpans = currentWrapSpans.slice(0, s); + afterSpans = currentWrapSpans.slice(s + 1); + const beforeText = currentWrapSpans[s].text.substring(0, c); + const afterText = currentWrapSpans[s].text.substring(c); + if (beforeText.length > 0) { + beforeSpans.push({ + text: beforeText, + meta: currentWrapSpans[s].meta + }); + } + if (afterText.length > 0) { + afterSpans.unshift({ + text: afterText, + meta: currentWrapSpans[s].meta + }); + } + } + let largestOffset = wrapCharacterOffsets[wrapCharacterOffsets.length-1]; + if (largestOffset > totalTextDirectionSize) { + totalTextDirectionSize = largestOffset; + } + const newWrap = { + characterOffsets: wrapCharacterOffsets, + spans: beforeSpans + }; + newWrap.characterOffsets = newWrap.characterOffsets.slice(0, this.get_wrap_text(newWrap).length + 1); + lineWraps.push(newWrap); + currentWrapSpans = afterSpans; + wrapAccumulativeSize = 0; + wrapCharacterOffsets = [0]; + s = -1; + break; + } else { + wrapCharacterOffsets.push(wrapAccumulativeSize); + } + } + if (s === -1) { + continue; + } + } + if (currentWrapSpans.length > 0) { + let largestOffset = wrapCharacterOffsets[wrapCharacterOffsets.length-1]; + if (largestOffset > totalTextDirectionSize) { + totalTextDirectionSize = largestOffset; + } + lineWraps.push({ + characterOffsets: wrapCharacterOffsets, + spans: currentWrapSpans + }); + } + lineRenderInfo.lines.push({ + firstWrapIndex: 0, + wraps: lineWraps + }); + } + + // Adjust offsets for alignment along the text direction + if ((isHorizontalTextDirection && halign !== 'left') || (!isHorizontalTextDirection && valign !== 'top')) { + const maxTextDirectionSize = boundary === 'dynamic' ? totalTextDirectionSize : (isHorizontalTextDirection ? layer.width : layer.height); + for (let line of lineRenderInfo.lines) { + for (let wrap of line.wraps) { + const isCentered = (isHorizontalTextDirection && halign == 'center') || (!isHorizontalTextDirection && valign === 'middle'); + const lastSpan = wrap.spans[wrap.spans.length - 1]; + const wrapSize = wrap.characterOffsets[wrap.characterOffsets.length - 1 - (lastSpan.text[lastSpan.text.length - 1] === ' ' ? 1 : 0)]; + const startOffset = (isCentered ? maxTextDirectionSize / 2 : maxTextDirectionSize) - (isCentered ? wrapSize / 2 : wrapSize); + if (startOffset > 0) { + for (let oi = 0; oi < wrap.characterOffsets.length; oi++) { + wrap.characterOffsets[oi] += startOffset; + } + } + } + } + } + + // Determine the size of each line (e.g. line height if horizontal typing direction) + let wrapSizeAccumulator = 0; + let wrapCounter = 0; + for (let line of lineRenderInfo.lines) { + line.firstWrapIndex = wrapCounter; + for (let wrap of line.wraps) { + let ascenderSize = 0; + let descenderSize = 0; + for (let span of wrap.spans) { + let fontMetrics; + const family = span.meta.family || metaDefaults.family; + const leading = span.meta.leading != null ? span.meta.leading : metaDefaults.leading; + if (isHorizontalTextDirection) { + fontMetrics = this.get_span_font_metrics(span, !fontLoadMap.get(family)); + } else { + ctx.font = + ' ' + (span.meta.italic ? 'italic' : '') + + ' ' + (span.meta.bold ? 'bold' : '') + + ' ' + (span.meta.size || metaDefaults.size) + 'px' + + ' ' + family; + } + let spanAscenderSize = isHorizontalTextDirection ? fontMetrics.baseline : ctx.measureText(character).width; + let spanDescenderSize = isHorizontalTextDirection ? Math.abs(fontMetrics.baseline - fontMetrics.height) : ctx.measureText(character).width; + if (leading) { + spanAscenderSize += leading; + if (spanAscenderSize < 0) { + spanDescenderSize += spanAscenderSize; + spanAscenderSize = 0; + if (spanDescenderSize < 0) { + spanDescenderSize = 0; + } + } + } + if (spanAscenderSize > ascenderSize) { + ascenderSize = spanAscenderSize; + } + if (spanDescenderSize > descenderSize) { + descenderSize = spanDescenderSize; + } + } + let lineSize = ascenderSize + descenderSize; + lineRenderInfo.wrapSizes.push({ size: lineSize, offset: wrapSizeAccumulator, baseline: ascenderSize }); + wrapSizeAccumulator += lineSize; + wrapCounter++; + } + } + totalWrapDirectionSize = wrapSizeAccumulator; + + this.lastCalculatedLayerWidth = layer.width; + this.lastCalculatedLayerHeight = layer.height; + this.textBoundaryWidth = Math.max(1, Math.round(isHorizontalTextDirection ? totalTextDirectionSize : totalWrapDirectionSize)); + this.textBoundaryHeight = Math.max(1, Math.round(isHorizontalTextDirection ? totalWrapDirectionSize : totalTextDirectionSize)); + this.lineRenderInfo = lineRenderInfo; + } + + render(ctx, layer) { + if (config.need_render_changed_params || this.hasValueChanged || layer.width != this.lastCalculatedLayerWidth || layer.height != this.lastCalculatedLayerHeight || !this.textBoundaryWidth || !this.textBoundaryHeight) { + this.calculate_text_placement(ctx, layer); + } + + if (!this.lineRenderInfo) return; + + try { + + let options = options || {}; + let isSelectionEmpty = this.selection.is_empty(); + + ctx.textAlign = 'left'; + ctx.textBaseline = 'alphabetic'; + + const boundary = layer.params.boundary; + let drawOffsetTop = layer.y + 1; + let drawOffsetLeft = layer.x + 1; + const textDirection = layer.params.text_direction; + const wrapDirection = layer.params.wrap_direction; + const isHorizontalTextDirection = ['ltr', 'rtl'].includes(textDirection); + const isNegativeTextDirection = ['rtl', 'btt'].includes(textDirection); + + const wrapSizes = this.lineRenderInfo.wrapSizes; + let lineIndex = 0; + let wrapIndex = 0; + const cursorLine = this.selection.isActiveSideEnd ? this.selection.end.line : this.selection.start.line; + const cursorCharacter = this.selection.isActiveSideEnd ? this.selection.end.character : this.selection.start.character; + if(layer.rotate){ + const alpha = (layer.rotate * Math.PI) / 180; + ctx.save(); + // Move the canvas to the center before rotating + ctx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); + ctx.rotate(alpha); + // Move it back after it + ctx.translate(-layer.x - layer.width / 2, -layer.y - layer.height / 2); + + } + for (let line of this.lineRenderInfo.lines) { + let lineLetterCount = 0; + for (let [localWrapIndex, wrap] of line.wraps.entries()) { + let cursorStartX = null; + let cursorStartY = null; + let cursorSize = null; + let characterIndex = 0; + const characterOffsets = wrap.characterOffsets; + for (let [spanIndex, span] of wrap.spans.entries()) { + const kerning = span.meta.kerning != null ? span.meta.kerning : metaDefaults.kerning; + const bold = span.meta.bold != null ? span.meta.bold : metaDefaults.bold; + const italic = span.meta.italic != null ? span.meta.italic : metaDefaults.italic; + const underline = span.meta.underline != null ? span.meta.underline : metaDefaults.underline; + const strikethrough = span.meta.strikethrough != null ? span.meta.strikethrough : metaDefaults.strikethrough; + const family = span.meta.family || metaDefaults.family; + + if (fontLoadMap.get(family) !== true) { + const variants = config.user_fonts[family] ? config.user_fonts[family].variants : undefined; + load_font_family({ family, variants }, () => { + this.hasValueChanged = true; + this.Base_layers.render(); + }); + } + + let fontMetrics; + if (underline || strikethrough) { + fontMetrics = this.get_span_font_metrics(span, !fontLoadMap.get(family)); + } + + // Set styles for drawing + ctx.font = + ' ' + (italic ? 'italic' : '') + + ' ' + (bold ? 'bold' : '') + + ' ' + Math.round(span.meta.size || metaDefaults.size) + 'px' + + ' ' + family; + const fill_color = span.meta.fill_color || metaDefaults.fill_color; + let fillStyle; + if (fill_color.startsWith('#')) { + fillStyle = fill_color; + } + const stroke_size = ((span.meta.stroke_size != null) ? span.meta.stroke_size : metaDefaults.stroke_size); + let strokeStyle; + if (stroke_size) { + const stroke_color = span.meta.stroke_color || metaDefaults.stroke_color; + if (stroke_color.startsWith('#')) { + strokeStyle = stroke_color; + } + ctx.lineWidth = stroke_size; + } else { + ctx.lineWidth = 0; + } + + + + // Loop through each letter in each span and draw it + for (let c = 0; c < span.text.length; c++) { + const letter = span.text.charAt(c); + const lineStart = Math.round(drawOffsetTop + wrapSizes[wrapIndex].offset); + const letterWidth = characterOffsets[characterIndex + 1] - characterOffsets[characterIndex]; + const letterHeight = Math.round(wrapSizes[wrapIndex].size); + const textDirectionOffset = drawOffsetLeft + characterOffsets[characterIndex]; + const wrapDirectionOffset = Math.round(drawOffsetTop + wrapSizes[wrapIndex].offset + wrapSizes[wrapIndex].baseline); + const letterDrawX = isHorizontalTextDirection ? textDirectionOffset + kerning : wrapDirectionOffset; + const letterDrawY = isHorizontalTextDirection ? wrapDirectionOffset : textDirectionOffset + kerning; + let isLetterSelected = false; + if (this.selection.isVisible) { + if (!isSelectionEmpty) { + isLetterSelected = ( + ( + this.selection.start.line === lineIndex && + this.selection.start.character <= lineLetterCount && + (this.selection.end.line > lineIndex || this.selection.end.character > lineLetterCount) + ) || + ( + this.selection.end.line === lineIndex && + this.selection.end.character > lineLetterCount && + (this.selection.start.line < lineIndex || this.selection.start.character <= lineLetterCount) + ) || + ( + this.selection.start.line < lineIndex && + this.selection.end.line > lineIndex + ) + ); + } + if (cursorLine === lineIndex) { + if (cursorCharacter === lineLetterCount) { + cursorStartX = (isHorizontalTextDirection ? textDirectionOffset : lineStart) - 0.5; + cursorStartY = (isHorizontalTextDirection ? lineStart : textDirectionOffset) - 0.5; + cursorSize = isHorizontalTextDirection ? letterHeight : letterWidth; + } + else if (cursorCharacter === lineLetterCount + 1 && localWrapIndex === line.wraps.length - 1 && spanIndex === wrap.spans.length - 1 && c === span.text.length - 1) { + cursorStartX = (isHorizontalTextDirection ? textDirectionOffset + letterWidth : lineStart) - 0.5; + cursorStartY = (isHorizontalTextDirection ? lineStart : textDirectionOffset + letterHeight) - 0.5; + cursorSize = isHorizontalTextDirection ? letterHeight : letterWidth; + } + } + } + if (isLetterSelected && this.editingCtx === ctx) { + const letterStartX = isHorizontalTextDirection ? textDirectionOffset : lineStart; + const letterStartY = isHorizontalTextDirection ? lineStart : textDirectionOffset; + const letterSizeX = isHorizontalTextDirection ? letterWidth : letterHeight; + const letterSizeY = isHorizontalTextDirection ? letterHeight : letterWidth; + ctx.fillStyle = this.selectionBackgroundColor + '22'; + ctx.fillRect(letterStartX, letterStartY, letterSizeX, letterSizeY); + ctx.strokeStyle = this.selectionBackgroundColor; + ctx.lineWidth = 0.75; + ctx.strokeRect(letterStartX, letterStartY, letterSizeX, letterSizeY); + ctx.lineWidth = stroke_size; + } + ctx.fillStyle = fillStyle; + ctx.strokeStyle = strokeStyle; + ctx.fillText(letter, letterDrawX, letterDrawY); + if (stroke_size) { + ctx.lineWidth = stroke_size; + ctx.strokeText(letter, letterDrawX, letterDrawY); + } + if (strikethrough) { + ctx.fillStyle = fillStyle; + ctx.lineWidth = Math.max(1, fontMetrics.height / 20); + ctx.fillRect(letterDrawX - 0.25 - kerning, letterDrawY - (fontMetrics.height * .28), letterWidth + 0.5, ctx.lineWidth); + } + if (underline) { + ctx.fillStyle = fillStyle; + ctx.lineWidth = Math.max(1, fontMetrics.height / 20); + ctx.fillRect(letterDrawX - 0.25 - kerning, letterDrawY + (ctx.lineWidth), letterWidth + 0.5, ctx.lineWidth); + } + characterIndex++; + lineLetterCount++; + } + + + + if (span.text.length === 0) { + if (cursorLine === lineIndex && cursorCharacter === lineLetterCount) { + const lineStart = Math.round(drawOffsetTop + wrapSizes[wrapIndex].offset); + const textDirectionOffset = drawOffsetLeft + characterOffsets[0] + (lineIndex === 0 ? (boundary === 'dynamic' ? 5 : 2) : 0); + const letterWidth = 3; + const letterHeight = Math.round(wrapSizes[wrapIndex].size); + cursorStartX = (isHorizontalTextDirection ? textDirectionOffset : lineStart) - 0.5; + cursorStartY = (isHorizontalTextDirection ? lineStart : textDirectionOffset) - 0.5; + cursorSize = isHorizontalTextDirection ? letterHeight : letterWidth; + } + } + } + + // Draw cursor + if (this.selection.isCursorVisible /*&& this.selection.isBlinkVisible*/ && cursorStartX && this.editingCtx == ctx) { + ctx.lineCap = 'butt'; + ctx.strokeStyle = '#55555577'; + ctx.lineWidth = 3; + ctx.beginPath(); + ctx.moveTo(cursorStartX, cursorStartY + 1); + ctx.lineTo(cursorStartX, cursorStartY + cursorSize - 1); + if (cursorSize > 14) { + ctx.moveTo(cursorStartX - 3, cursorStartY + 2); + ctx.lineTo(cursorStartX + 3, cursorStartY + 2); + ctx.moveTo(cursorStartX - 3, cursorStartY + cursorSize - 2); + ctx.lineTo(cursorStartX + 3, cursorStartY + cursorSize - 2); + } + ctx.stroke(); + ctx.strokeStyle = '#ffffffff'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(cursorStartX, cursorStartY + 2); + ctx.lineTo(cursorStartX, cursorStartY + cursorSize - 2); + if (cursorSize > 14) { + ctx.moveTo(cursorStartX - 2, cursorStartY + 2); + ctx.lineTo(cursorStartX + 2, cursorStartY + 2); + ctx.moveTo(cursorStartX - 2, cursorStartY + cursorSize - 2); + ctx.lineTo(cursorStartX + 2, cursorStartY + cursorSize - 2); + } + ctx.stroke(); + } + wrapIndex++; + } + lineIndex++; + } + if(layer.rotate){ + ctx.restore(); + } + } catch (error) { + console.warn(error); + } + + this.hasValueChanged = false; + } +} + +class Google_fonts_search_class { + constructor() { + this.POP = new Dialog_class(); + this.GUI_tools = new GUI_tools_class(); + this.popup = null; + this.fontsPerPage = 8; + this.dialogContentNode = null; + this.fontListNode = null; + this.fontList = []; + this.fontListFiltered = []; + this.selectedFonts = {}; + this.searchTimeoutHandle = null; + } + + render_font_list(page) { + page = page || 1; + const pageCount = Math.ceil(this.fontListFiltered.length / 8); + const startIndex = (page - 1) * this.fontsPerPage; + let html = '
'; + for (let i = startIndex; i < startIndex + this.fontsPerPage; i++) { + const font = this.fontListFiltered[i]; + if (!font) break; + const isSelected = !!this.selectedFonts[font.family]; + load_font_family({ family: font.family, variants: font.variants }); + html += ` +
+ + +
+ `; + } + html += ` +
+ + + `; + this.fontListNode.innerHTML = html; + + // Attempt to remove vertical scroll by decreasing page size. + if (this.fontsPerPage > 3 && this.dialogContentNode.scrollHeight > this.dialogContentNode.clientHeight) { + this.fontsPerPage--; + this.render_font_list(page); + return; + } + + // Handle checkbox + this.fontListNode.querySelectorAll('input[type="checkbox"]').forEach((checkbox) => { + checkbox.addEventListener('change', (e) => { + if (checkbox.checked) { + this.selectedFonts[checkbox.value] = this.fontListFiltered + .slice(startIndex, startIndex + this.fontsPerPage) + .filter((font) => { return font.family === checkbox.value; })[0]; + } else { + delete this.selectedFonts[checkbox.value]; + } + }); + }); + + // Handle pagination + this.fontListNode.querySelector('.pagination').addEventListener('click', (e) => { + const page = parseInt(e.target.getAttribute('data-page'), 10); + this.render_font_list(page); + }); + } + + show() { + this.POP.show({ + title: 'Search for Font', + params: [ + { name: "query", title: "Search:", value: '', prevent_submission: true } + ], + on_load: (params, popup) => { + this.popup = popup; + var node = document.createElement("div"); + this.dialogContentNode = popup.el.querySelector('.dialog_content'); + this.dialogContentNode.appendChild(node); + this.fontListNode = node; + + const queryInput = popup.el.querySelector('#pop_data_query'); + queryInput.addEventListener('input', (e) => { + const query = (e.target.value || '').toLowerCase(); + if (!query) { + this.fontListFiltered = this.fontList; + this.render_font_list(); + } else { + clearTimeout(this.searchTimeoutHandle); + this.searchTimeoutHandle = setTimeout(() => { + this.fontListFiltered = []; + for (let i = 0; i < this.fontList.length; i++) { + const fontFamily = this.fontList[i].family.toLowerCase(); + if (fontFamily.includes(query)) { + this.fontListFiltered.push(this.fontList[i]); + } + } + this.render_font_list(); + }, 350); + } + }); + + const apiKey = config.google_webfonts_key; + $.getJSON(`https://www.googleapis.com/webfonts/v1/webfonts?key=${apiKey}&sort=popularity`, (data) => { + this.fontList = data.items; + this.fontListFiltered = data.items; + this.render_font_list(); + }).fail(function () { + alertify.error('Error loading the list of fonts from Google.'); + }); + }, + on_finish: () => { + this.popup = null; + this.POP = null; + if (Object.keys(this.selectedFonts).length > 0) { + let firstFont = null; + for (let font in this.selectedFonts) { + if (!firstFont) { + firstFont = font; + } + config.user_fonts[font] = this.selectedFonts[font]; + } + app.GUI.GUI_tools.action_data().attributes.font.value = firstFont; + app.GUI.GUI_tools.show_action_attributes(); + try { + const changeEvent = new Event('change'); + document.querySelector('#action_attributes select#font').dispatchEvent(changeEvent); + } catch (error) { + console.warn('Application markup may have changed, ', error); + } + } + } + }); + } +} + + +class Text_class extends Base_tools_class { + + constructor(ctx) { + super(); + this.Base_layers = new Base_layers_class(); + this.GUI_tools = new GUI_tools_class(); + this.Helper = new Helper_class(); + this.ctx = ctx; + this.name = 'text'; + this.layer = {}; + this.creating = false; + this.selecting = false; + this.resizing = false; + this.focused = false; + this.focusedValue = null; + this.mousedownX = 0; + this.mousedownY = 0; + this.mousedownBounds = {}; + this.is_fonts_loaded = false; + if (ctx) { + this.selection = { + x: null, + y: null, + width: null, + height: null, + }; + var sel_config = { + enable_background: false, + enable_borders: true, + enable_controls: true, + enable_rotation: true, + enable_move: false, + data_function: () => { + return this.selection; + }, + }; + this.Base_selection = new Base_selection_class(ctx, sel_config, this.name); + + // Need a textarea in order to listen for keyboard inputs in an accessible, multi-platform independent way + this.textarea = document.createElement('textarea'); + this.textarea.id = 'text_tool_keyboard_input'; + this.textarea.setAttribute('autocorrect', 'off'); + this.textarea.setAttribute('autocapitalize', 'off'); + this.textarea.setAttribute('autocomplete', 'off'); + this.textarea.setAttribute('spellcheck', 'false'); + this.textarea.style = `position: absolute; top: 0; left: 0; padding: 0; width: 1px; height: 1px; background: transparent; border: none; outline: none; color: transparent; opacity: 0.01; pointer-events: none;`; + document.body.appendChild(this.textarea); + + this.textarea.addEventListener('focus', () => { + this.focused = true; + let editor = this.get_editor(this.layer); + if (editor) { + this.focusedValue = JSON.stringify(editor.document.lines); + } + }, true); + + this.textarea.addEventListener('blur', () => { + this.focused = false; + let editor = this.get_editor(this.layer); + if (editor) { + let value = JSON.stringify(editor.document.lines); + if (this.focusedValue !== value) { + this.layer.data = JSON.parse(this.focusedValue); + app.State.do_action( + new app.Actions.Update_layer_action(this.layer.id, { data: JSON.parse(value) }) + ); + } + } + this.focusedValue = null; + this.Base_layers.render(); + }, true); + + let isComposing = false; + let beforeImeText = ""; + this.textarea.addEventListener('compositionstart', () => { + beforeImeText = ""; + isComposing = true; + if (config.layer) { + const editor = this.get_editor(config.layer); + beforeImeText = editor.get_complete_text(); + } + }); + + this.textarea.addEventListener('compositionend', (e) => { + const editor = this.get_editor(config.layer); + editor.set_IME_position(e.target.value); + beforeImeText = ""; + isComposing = false; + e.target.value = ''; + }); + + this.textarea.addEventListener('input', (e) => { + if(isComposing){ + const editor = this.get_editor(config.layer); + editor.replace_entire_IME_text(beforeImeText, e.target.value); + this.Base_layers.render(); + this.extend_fixed_bounds(config.layer, editor); + } + else if (config.layer) { + const editor = this.get_editor(config.layer); + editor.insert_text_at_current_position(e.target.value); + e.target.value = ''; + this.Base_layers.render(); + this.extend_fixed_bounds(config.layer, editor); + } + }, true); + + this.textarea.addEventListener('keydown', (e) => { + if (config.layer) { + let handled = true; + const editor = this.get_editor(config.layer); + switch (e.key) { + case 'Backspace': + editor.delete_character_at_current_position(false); + break; + case 'Delete': + editor.delete_character_at_current_position(true); + break; + case 'Home': + editor.selection.move_line_start(e.shiftKey); + break; + case 'End': + editor.selection.move_line_end(e.shiftKey); + break; + case 'Left': case 'ArrowLeft': + if (!e.shiftKey && !editor.selection.is_empty()) { + editor.selection.isActiveSideEnd = false; + editor.selection.move_character_previous(0, false); + } else if (e.ctrlKey) { + editor.selection.move_word_previous(e.shiftKey); + } else { + editor.selection.move_character_previous(1, e.shiftKey); + } + break; + case 'Right': case 'ArrowRight': + if (!e.shiftKey && !editor.selection.is_empty()) { + editor.selection.isActiveSideEnd = true; + editor.selection.move_character_next(0, false); + } else if (e.ctrlKey) { + editor.selection.move_word_next(e.shiftKey); + } else { + editor.selection.move_character_next(1, e.shiftKey); + } + break; + case 'Up': case 'ArrowUp': + editor.selection.move_line_previous(1, e.shiftKey); + break; + case 'Down': case 'ArrowDown': + editor.selection.move_line_next(1, e.shiftKey); + break; + case 'a': + if (e.ctrlKey) { + editor.selection.set_position(0, 0); + const lastLine = editor.document.lines.length - 1; + editor.selection.set_position(lastLine, editor.document.get_line_character_count(lastLine), true); + break; + } + case 'b': + if (e.ctrlKey) { + e.preventDefault(); + document.querySelector('#action_attributes #bold').click(); + break; + } + case 'c': + if (e.ctrlKey) { + e.preventDefault(); + this.textarea.value = editor.selection.get_text(); + this.textarea.select(); + this.textarea.setSelectionRange(0, 99999); + document.execCommand('copy'); + this.textarea.value = ''; + break; + } + case 'i': + if (e.ctrlKey) { + e.preventDefault(); + document.querySelector('#action_attributes #italic').click(); + break; + } + case 'u': + if (e.ctrlKey) { + e.preventDefault(); + document.querySelector('#action_attributes #underline').click(); + break; + } + case 'x': + if (e.ctrlKey) { + e.preventDefault(); + this.textarea.value = editor.selection.get_text(); + this.textarea.select(); + this.textarea.setSelectionRange(0, 99999); + document.execCommand('copy'); + this.textarea.value = ''; + editor.delete_selection(); + break; + } + default: + handled = false; + } + if (handled) { + this.update_tool_attributes(config.layer, editor); + this.Base_layers.render(); + } + this.extend_fixed_bounds(config.layer, editor); + return !handled; + } + }, true); + } + } + + dragStart(event) { + if (config.TOOL.name != this.name) + return; + this.mousedown(event); + } + + dragMove(event) { + if (config.TOOL.name != this.name) + return; + this.mousemove(event); + } + + dragEnd(event) { + if (config.TOOL.name != this.name) + return; + this.mouseup(event); + } + + load() { + // Mouse events + document.addEventListener('mousedown', (event) => { + this.dragStart(event); + }); + document.addEventListener('mousemove', (event) => { + this.dragMove(event); + }); + document.addEventListener('mouseup', (event) => { + this.dragEnd(event); + }); + document.addEventListener('dblclick', (event) => { + this.doubleClick(event); + }); + + // Touch events + document.addEventListener('touchstart', (event) => { + this.dragStart(event); + }); + document.addEventListener('touchmove', (event) => { + this.dragMove(event); + }); + document.addEventListener('touchend', (event) => { + this.dragEnd(event); + }); + } + + mousedown(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) + return; + + this.creating = false; + this.selecting = false; + this.resizing = false; + + this.mousedownX = mouse.x; + this.mousedownY = mouse.y; + this.mousedownBounds = { + x: config.layer.x, + y: config.layer.y, + width: config.layer.width, + height: config.layer.height, + boundary: config.layer.params.boundary + }; + + if (this.Base_selection.mouse_lock !== null) { + this.resizing = true; + return; + } + + const existingLayer = this.get_text_layer_at_mouse(e); + if (existingLayer) { + this.selecting = true; + this.layer = existingLayer; + const editor = this.get_editor(this.layer); + editor.trigger_cursor_start(this.layer, -1 + mouse.x - this.layer.x, mouse.y - this.layer.y); + app.State.do_action( + new app.Actions.Bundle_action('select_text_layer', 'Select Text Layer', [ + new app.Actions.Select_layer_action(existingLayer.id), + new app.Actions.Set_selection_action(this.layer.x, this.layer.y, this.layer.width, this.layer.height) + ]) + ); + } + else { + // Create a new text layer + this.creating = true; + const layer = { + type: this.name, + params: { + boundary: 'dynamic', + kerning: 'metrics', + text_direction: 'ltr', + wrap_direction: 'ttb', + halign: 'left', + valign: 'top', + wrap: 'letter' + }, + render_function: [this.name, 'render'], + x: mouse.x, + y: mouse.y, + rotate: 0, + is_vector: true, + }; + app.State.do_action( + new app.Actions.Bundle_action('new_text_layer', 'New Text Layer', [ + new app.Actions.Insert_layer_action(layer), + new app.Actions.Set_selection_action(mouse.x, mouse.y, 0, 0) + ]) + ); + this.layer = config.layer; + } + } + + mousemove(e) { + var mouse = this.get_mouse_info(e); + if (mouse.is_drag == false) + return; + if (mouse.click_valid == false) { + return; + } + + if (this.resizing) { + config.layer.x = this.selection.x; + config.layer.y = this.selection.y; + config.layer.width = this.selection.width; + config.layer.height = this.selection.height; + if (config.layer.params.boundary === 'dynamic') { + config.layer.params.boundary = 'box'; + } + } + else if (this.creating) { + const width = Math.abs(mouse.x - this.mousedownX); + const height = Math.abs(mouse.y - this.mousedownY); + + //more data + if (config.layer.params.boundary === 'dynamic') { + config.layer.params.boundary = 'box'; + } + config.layer.x = Math.min(mouse.x, this.mousedownX); + config.layer.y = Math.min(mouse.y, this.mousedownY); + config.layer.width = width; + config.layer.height = height; + } else { + this.get_editor(this.layer).trigger_cursor_move(this.layer, -1 + mouse.x - this.layer.x, mouse.y - this.layer.y); + } + this.Base_layers.render(); + } + + mouseup(e) { + var mouse = this.get_mouse_info(e); + if (mouse.click_valid == false) { + return; + } + const editor = this.get_editor(this.layer); + + if (this.resizing) { + config.layer.x = this.mousedownBounds.x; + config.layer.y = this.mousedownBounds.y; + config.layer.width = this.mousedownBounds.width; + config.layer.height = this.mousedownBounds.height; + const new_params = JSON.parse(JSON.stringify(config.layer.params)); + new_params.boundary = config.layer.params.boundary; + config.layer.params.boundary = this.mousedownBounds.boundary; + app.State.do_action( + new app.Actions.Bundle_action('resize_text_layer', 'Resize Text Layer', [ + new app.Actions.Update_layer_action(config.layer.id, { + x: this.selection.x, + y: this.selection.y, + width: this.selection.width, + height: this.selection.height, + params: new_params + }), + new app.Actions.Set_selection_action(this.selection.x, this.selection.y, this.selection.width, this.selection.height) + ]) + ); + } + else if (this.creating) { + let width = Math.abs(mouse.x - this.mousedownX); + let height = Math.abs(mouse.y - this.mousedownY); + + if (width == 0 && height == 0) { + // Same coordinates - let render figure out dynamic width + width = 1; + height = 1; + } + app.State.do_action( + new app.Actions.Bundle_action('resize_text_layer', 'Resize Text Layer', [ + new app.Actions.Update_layer_action(config.layer.id, { + x: Math.min(mouse.x, this.mousedownX), + y: Math.min(mouse.y, this.mousedownY), + width, + height + }) + ]), + { merge_with_history: 'new_text_layer' } + ); + this.textarea.focus(); + } + else if (this.selecting) { + editor.trigger_cursor_end(); + this.textarea.focus(); + + if (editor.selection.is_empty() && editor.document.queuedMetaChanges) { + let meta = {}; + const existingMeta = editor.document.get_meta_range(editor.selection.start.line, editor.selection.start.character, editor.selection.end.line, editor.selection.end.character); + for (let metaKey in existingMeta) { + meta[metaKey] = editor.document.queuedMetaChanges[metaKey] != null ? editor.document.queuedMetaChanges[metaKey] : existingMeta[metaKey][0]; + } + } else { + editor.document.queuedMetaChanges = null; + this.update_tool_attributes(this.layer, editor); + } + } + + // Resize layer based on text boundaries. + this.extend_fixed_bounds(this.layer, editor); + this.Base_layers.render(); + + // Center layer on mouse if not click & drag + if (this.creating && config.layer.params.boundary === 'dynamic') { + requestAnimationFrame(() => { + app.State.do_action( + new app.Actions.Update_layer_action(config.layer.id, { + x: config.layer.x - config.layer.width / 2, + y: config.layer.y - config.layer.height / 2 + }), + { merge_with_history: 'new_text_layer' } + ); + }); + } + + this.resizing = false; + this.selecting = false; + this.creating = false; + } + + + doubleClick(event) { + if (document.activeElement === this.textarea) { + const editor = this.get_editor(this.layer); + if (editor.selection.is_empty()) { + const position = editor.selection.get_position(); + const wordStart = editor.document.get_word_start_position(position.line, position.character, true); + const wordEnd = editor.document.get_word_end_position(position.line, position.character, true); + editor.selection.set_position(wordStart.line, wordStart.character); + editor.selection.set_position(wordEnd.line, wordEnd.character, true); + this.update_tool_attributes(this.layer, editor); + } + } + } + + on_params_update(param) { + const editor = this.get_editor(config.layer); + const value = param.value; + const meta = {}; + let returnValue = undefined; + switch (param.key) { + case 'font': + if (value.includes('...')) { + returnValue = { + new_values: { + font: '' + } + }; + new Google_fonts_search_class().show(); + } + else if (value) meta.family = value; + break; + case 'size': + if (value) meta.size = value; + break; + case 'bold': + meta.bold = value; + break; + case 'italic': + meta.italic = value; + break; + case 'underline': + meta.underline = value; + break; + case 'strikethrough': + meta.strikethrough = value; + break; + case 'fill': + if (value) meta.fill_color = value; + break; + case 'stroke': + if (value) meta.stroke_color = value; + break; + case 'stroke_size': + if (!isNaN(value)) meta.stroke_size = value; + break; + case 'kerning': + if (!isNaN(value)) meta.kerning = value; + break; + case 'leading': + if (!isNaN(value)) meta.leading = value; + break; + } + if (editor.selection.is_empty()) { + if (!editor.document.queuedMetaChanges) { + editor.document.queuedMetaChanges = {}; + } + for (let metaKey in meta) { + editor.document.queuedMetaChanges[metaKey] = meta[metaKey]; + } + } else { + editor.document.queuedMetaChanges = null; + let oldData = JSON.parse(JSON.stringify(editor.document.lines)); + editor.document.set_meta_range(editor.selection.start.line, editor.selection.start.character, editor.selection.end.line, editor.selection.end.character, meta); + editor.hasValueChanged = true; + this.layer.data = oldData; + app.State.do_action( + new app.Actions.Update_layer_action(this.layer.id, { data: JSON.parse(JSON.stringify(editor.document.lines)) }) + ); + this.Base_layers.render(); + } + return returnValue; + } + + update_tool_attributes(layer, editor) { + if (layer && layer.params) { + const meta = editor.document.get_meta_range(editor.selection.start.line, editor.selection.start.character, editor.selection.end.line, editor.selection.end.character); + const toolAttributes = this.GUI_tools.action_data().attributes; + toolAttributes.font.value = meta.family.length === 1 ? meta.family[0] : ''; + toolAttributes.size = meta.size.length === 1 ? meta.size[0] : parseFloat(null); + toolAttributes.bold.value = meta.bold.includes(false) ? false : true; + toolAttributes.italic.value = meta.italic.includes(false) ? false : true; + toolAttributes.underline.value = meta.underline.includes(false) ? false : true; + toolAttributes.strikethrough.value = meta.strikethrough.includes(false) ? false : true; + toolAttributes.fill = meta.fill_color.length === 1 ? meta.fill_color[0] : '#000000'; + toolAttributes.stroke = meta.stroke_color.length === 1 ? meta.stroke_color[0] : '#000000'; + toolAttributes.stroke_size.value = meta.stroke_size.length === 1 ? meta.stroke_size[0] : parseFloat(null); + toolAttributes.kerning.value = meta.kerning.length === 1 ? meta.kerning[0] : parseFloat(null); + toolAttributes.leading.value = meta.leading.length === 1 ? meta.leading[0] : parseFloat(null); + this.GUI_tools.show_action_attributes(); + } + } + + resize_to_dynamic_bounds(layer, editor) { + if (layer && layer.params && layer.params.boundary === 'dynamic') { + let new_width = Math.max(9, editor.textBoundaryWidth + 1); + let new_height = Math.max(9, editor.textBoundaryHeight + 1); + config.layer.width = new_width; + config.layer.height = new_height; + } + } + + extend_fixed_bounds(layer, editor) { + if (layer && layer.params && layer.params.boundary !== 'dynamic') { + const isHorizontalTextDirection = ['ltr', 'rtl'].includes(layer.params.textDirection); + let new_width = layer.width; + let new_height = layer.height; + if (isHorizontalTextDirection) { + new_width = Math.max(editor.textBoundaryWidth + 1, new_width); + } else { + new_height = Math.max(editor.textBoundaryHeight + 1, new_height); + } + config.layer.width = new_width; + config.layer.height = new_height; + } + } + + render(ctx, layer) { + if (layer.width == 0 && layer.height == 0) + return; + var params = layer.params; + + const isActiveLayerAndTextTool = layer === config.layer && config.TOOL.name === 'text'; + const editor = this.get_editor(layer); + editor.selection.set_visible(isActiveLayerAndTextTool); + editor.selection.set_cursor_visible(isActiveLayerAndTextTool && (this.selecting || this.focused)); + editor.render(ctx, layer); + if (layer === config.layer) { + this.resize_to_dynamic_bounds(layer, editor); + } + if (!this.resizing && isActiveLayerAndTextTool) { + this.selection.x = layer.x; + this.selection.y = layer.y; + this.selection.width = layer.width; + this.selection.height = layer.height; + this.selection.rotate = layer.rotate; + } else if (config.layer.type !== 'text') { + this.selection.x = -100000; + this.selection.y = -100000; + this.selection.width = 0; + this.selection.height = 0; + } + } + + get_editor(layer) { + let editor = layerEditors.get(layer); + if (!editor) { + editor = new Text_editor_class(); + + // Convert legacy to new format + if (layer.params && layer.params.text) { + const params = layer.params; + let lines = []; + const textLines = layer.params.text.split('\n'); + const family = params.family && params.family.value? params.family.value : params.family; + for (const textLine of textLines) { + lines.push([ + { + text: textLine, + meta: { + family, + size: params.size, + bold: params.bold, + italic: params.italic, + fill_color: params.stroke ? '#ffffff00' : layer.color, + stroke_color: params.stroke ? layer.color : '#ffffff00', + stroke_size: params.stroke ? params.stroke_size : 0, + leading: 0 + } + } + ]); + } + params.boundary = 'box'; + params.kerning = 'metrics'; + params.halign = params.align ? (params.align.value ? params.align.value : params.align).toLowerCase() : 'left'; + params.valign = 'top'; + params.text_direction = 'ltr'; + params.wrap_direction = 'ttb'; + params.wrap = 'word'; + delete params.text; + delete params.family; + delete params.size; + delete params.bold; + delete params.italic; + delete params.stroke; + delete params.stroke_size; + delete params.align; + layer.data = lines; + layer.x -= 1; + + // Change leading offset so line height matches legacy line height calculation... need to load the font first to do this. + // This is an approximate calculation, but seems to be pretty close. + load_font_family({ family }, () => { + const line = layer.data[0]; + if (!line) return; + const span = line[0]; + if (!span) return; + const fontMetrics = editor.get_span_font_metrics(span, !fontLoadMap.get(span.meta.family || metaDefaults.family)); + const topBounds = fontMetrics.calculate_letter_bounds('M', 'top'); + span.meta.leading = (span.meta.size || metaDefaults.size) - fontMetrics.height; + layer.y += Math.abs(span.meta.leading) - (fontMetrics.baseline - topBounds.bottom); + editor.hasValueChanged = true; + editor.Base_layers.render(); + }); + } + + // Create initial layer data if new layer + if (!layer.data) { + const params = this.getParams(); + layer.data = [[{ + text: '', + meta: { + family: params.font.value !== metaDefaults.family && params.font.value ? params.font.value : undefined, + size: params.size !== metaDefaults.size && !isNaN(params.size) ? params.size : undefined, + bold: params.bold.value !== metaDefaults.bold ? params.bold.value : undefined, + italic: params.italic.value !== metaDefaults.italic ? params.italic.value : undefined, + underline: params.underline.value !== metaDefaults.underline ? params.underline.value : undefined, + strikethrough: params.strikethrough.value !== metaDefaults.strikethrough ? params.strikethrough.value : undefined, + fill_color: params.fill !== metaDefaults.fill_color ? params.fill : undefined, + stroke_color: params.stroke !== metaDefaults.stroke_color ? params.stroke : undefined, + stroke_size: params.stroke_size !== metaDefaults.stroke_size && !isNaN(params.stroke_size) ? params.stroke_size : undefined, + kerning: params.kerning !== metaDefaults.kerning && !isNaN(params.kerning) ? params.kerning : undefined, + leading: params.leading !== metaDefaults.leading && !isNaN(params.leading) ? params.leading : undefined + } + }]]; + } + + editor.set_lines(layer.data); + editor.Base_layers = this.Base_layers; + editor.layer = layer; + layerEditors.set(layer, editor); + } + if (layer._needs_update_data) { + delete layer._needs_update_data; + editor.hasValueChanged = true; + editor.set_lines(JSON.parse(JSON.stringify(layer.data))); + } + return editor; + } + + get_text_layer_at_mouse(e) { + const layers_sorted = this.Base_layers.get_sorted_layers(); + if (config.layer.type === 'text') { + layers_sorted.unshift(config.layer); + } + const mouse = this.get_mouse_info(e); + const clickableMargin = 5; + for (let layer of layers_sorted) { + if (layer.type === 'text') { + // TODO - account for rotation + if (mouse.x >= layer.x - clickableMargin && mouse.x <= layer.x + layer.width + clickableMargin && mouse.y >= layer.y - clickableMargin && mouse.y <= layer.y + layer.height + clickableMargin) { + return layer; + } + } + } + return null; + } + +} + +export default Text_class; \ No newline at end of file diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx deleted file mode 100644 index 5cc5991..0000000 --- a/frontend/src/main.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import './index.css' - -ReactDOM.createRoot(document.getElementById('root')).render( - - - , -) diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js deleted file mode 100644 index 164910d..0000000 --- a/frontend/src/utils/api.js +++ /dev/null @@ -1,241 +0,0 @@ -import axios from 'axios'; - -// Use relative URLs to go through nginx proxy, or use env variable for direct connection -const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''; - -const api = axios.create({ - baseURL: API_BASE_URL, - headers: { - 'Content-Type': 'application/json', - }, -}); - -export const projectsApi = { - // Create a new project - create: async (name) => { - const response = await api.post('/projects/', { name }); - return response.data; - }, - - // List all projects - list: async () => { - const response = await api.get('/projects/'); - return response.data; - }, - - // Get a specific project - get: async (projectId) => { - const response = await api.get(`/projects/${projectId}`); - return response.data; - }, - - // Delete a project - delete: async (projectId) => { - const response = await api.delete(`/projects/${projectId}`); - return response.data; - }, - - // Upload image to project - uploadImage: async (projectId, file) => { - const formData = new FormData(); - formData.append('file', file); - - const response = await api.post(`/projects/${projectId}/upload`, formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - return response.data; - }, - - // Get edits for a project - getEdits: async (projectId) => { - const response = await api.get(`/projects/${projectId}/edits`); - return response.data; - }, - - // Get image URLs - getOriginalImageUrl: (projectId) => `${API_BASE_URL}/projects/${projectId}/original`, - getCurrentImageUrl: (projectId) => `${API_BASE_URL}/projects/${projectId}/current`, - getEditResultUrl: (projectId, editId) => `${API_BASE_URL}/projects/${projectId}/history/${editId}/result`, -}; - -export const editsApi = { - // Create a new edit (Fix button) - create: async (projectId, editData) => { - const response = await api.post(`/edits/projects/${projectId}/fix`, editData); - return response.data; - }, - - // Get edit status - get: async (editId) => { - const response = await api.get(`/edits/${editId}`); - return response.data; - }, - - // Revert to a specific edit - revert: async (projectId, editId) => { - const response = await api.post(`/edits/projects/${projectId}/revert/${editId}`); - return response.data; - }, - - // Reset to original - reset: async (projectId) => { - const response = await api.post(`/edits/projects/${projectId}/reset`); - return response.data; - }, -}; - -export const patchesApi = { - // List all patches (eyes) - list: async (category = null, tags = null) => { - const params = new URLSearchParams(); - if (category) params.append('category', category); - if (tags) params.append('tags', tags); - const response = await api.get(`/patches/?${params.toString()}`); - return response.data; - }, - - // Get a specific patch - get: async (patchId) => { - const response = await api.get(`/patches/${patchId}`); - return response.data; - }, - - // Upload a new patch (eye) - create: async (name, file, category = 'eyes', tags = '') => { - const formData = new FormData(); - formData.append('name', name); - formData.append('source_type', 'imported'); - formData.append('category', category); - formData.append('tags', tags); - formData.append('file', file); - - const response = await api.post('/patches/', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - return response.data; - }, - - // Apply a patch to the project - apply: async (projectId, patchId, bbox, featherPx = 5) => { - const formData = new FormData(); - formData.append('project_id', projectId); - formData.append('patch_id', patchId); - formData.append('bbox', JSON.stringify(bbox)); - formData.append('feather_px', featherPx); - - const response = await api.post('/patches/apply', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - return response.data; - }, - - // Delete a patch - delete: async (patchId) => { - const response = await api.delete(`/patches/${patchId}`); - return response.data; - }, - - // Get patch image URL - getImageUrl: (patchId, thumbnail = false) => - `${API_BASE_URL}/patches/${patchId}/image${thumbnail ? '?thumbnail=true' : ''}`, -}; - -export const toolsApi = { - // Remove background from project image - removeBackground: async (projectId) => { - const formData = new FormData(); - formData.append('project_id', projectId); - - const response = await api.post('/tools/remove-background', formData, { - headers: { 'Content-Type': 'multipart/form-data' }, - responseType: 'blob', - }); - return response.data; - }, - - // Remove background and save as layer - removeBackgroundToLayer: async (projectId) => { - const formData = new FormData(); - formData.append('project_id', projectId); - - const response = await api.post('/tools/remove-background-to-layer', formData, { - headers: { 'Content-Type': 'multipart/form-data' }, - }); - return response.data; - }, - - // Smart select object at point - // Returns { polygon, bbox, mask_base64 } - smartSelect: async (projectId, x, y) => { - const formData = new FormData(); - formData.append('project_id', projectId); - formData.append('point_x', x); - formData.append('point_y', y); - formData.append('return_format', 'json'); - - const response = await api.post('/tools/smart-select', formData, { - headers: { 'Content-Type': 'multipart/form-data' }, - }); - return response.data; - }, - - // Select by color - // Returns { polygon, bbox, mask_base64, color, tolerance } - colorSelect: async (projectId, r, g, b, tolerance = 30) => { - const formData = new FormData(); - formData.append('project_id', projectId); - formData.append('color_r', r); - formData.append('color_g', g); - formData.append('color_b', b); - formData.append('tolerance', tolerance); - formData.append('return_format', 'json'); - - const response = await api.post('/tools/color-select', formData, { - headers: { 'Content-Type': 'multipart/form-data' }, - }); - return response.data; - }, - - // Extract object with mask - extractObject: async (projectId, maskBlob) => { - const formData = new FormData(); - formData.append('project_id', projectId); - formData.append('mask', maskBlob, 'mask.png'); - - const response = await api.post('/tools/extract-object', formData, { - headers: { 'Content-Type': 'multipart/form-data' }, - responseType: 'blob', - }); - return response.data; - }, - - // List layers - listLayers: async (projectId) => { - const response = await api.get(`/tools/layers/${projectId}`); - return response.data; - }, - - // Flatten layers - flattenLayers: async (projectId, layerOrder) => { - const formData = new FormData(); - formData.append('project_id', projectId); - formData.append('layer_order', JSON.stringify(layerOrder)); - - const response = await api.post('/tools/flatten-layers', formData, { - headers: { 'Content-Type': 'multipart/form-data' }, - }); - return response.data; - }, - - // Get layer image URL - getLayerImageUrl: (projectId, layerId) => - `${API_BASE_URL}/projects/${projectId}/layers/layer_${layerId}.png`, -}; - -export default api; diff --git a/frontend/tools/translator/config.php b/frontend/tools/translator/config.php new file mode 100644 index 0000000..5899050 --- /dev/null +++ b/frontend/tools/translator/config.php @@ -0,0 +1,32 @@ + + + + + + + Translator + + +

Translator

+
+ Helpers: + + + + +

+ Actions: + + or + + '.strtoupper($lang).' '; + } + ?> +

+ 0) { + try { + if ($_POST['action'] == 'Import') { + $translator->scan(); + $translator->extract(); + echo "
"; print_r($translator->strings); echo "
\n"; + } + if ($_POST['action'] == 'Filter') { + $translator->scan(); + $translator->extract(); + $translator->filter(); + echo "
"; print_r($translator->strings); echo "
\n"; + } + if ($_POST['action'] == 'Translate manually') { + //show form + $translator->prepare(); + + //translate + if (isset($_POST['in'])) { + $translation = $_POST['in']; + + $translator->scan(); + $translator->extract(); + $translator->filter(); + $translator->add_translation($translation); + $translator->show_merged(); + } + } + if ($_POST['action'] == 'Merge') { + $translator->merge(); + } + if (stripos($_POST['action'], 'Auto Translate') !== false) { + //prepare + $translator->scan(); + $translator->extract(); + $translator->filter(); + + $translator->auto_translate($_POST['action']); + } + if ($_POST['action'] == 'Generate empty.json') { + //prepare + $translator->scan(); + $translator->extract(); + $translator->filter(); + + $translator->save_empty(); + } + + } + catch (Exception $exc) { + echo '
ERROR: ' . $exc->getMessage() . '
'; + } + } + ?> +
+ + diff --git a/frontend/tools/translator/libs/GoogleTranslate.php b/frontend/tools/translator/libs/GoogleTranslate.php new file mode 100644 index 0000000..87d9e13 --- /dev/null +++ b/frontend/tools/translator/libs/GoogleTranslate.php @@ -0,0 +1,137 @@ + + * @copyright 2016 Adrián Barrio Andrés + * @license https://opensource.org/licenses/GPL-3.0 GNU General Public License 3.0 + * @version 2.0 + * @link https://statickidz.com/ + */ + +/** + * Main class GoogleTranslate + * + * @package GoogleTranslate + * + */ +class GoogleTranslate +{ + + /** + * Retrieves the translation of a text + * + * @param string $source + * Original language of the text on notation xx. For example: es, en, it, fr... + * @param string $target + * Language to which you want to translate the text in format xx. For example: es, en, it, fr... + * @param string $text + * Text that you want to translate + * + * @return string a simple string with the translation of the text in the target language + */ + public static function translate($source, $target, $text) + { + // Request translation + $response = self::requestTranslation($source, $target, $text); + + // Get translation text + // $response = self::getStringBetween("onmouseout=\"this.style.backgroundColor='#fff'\">", "", strval($response)); + + // Clean translation + $translation = self::getSentencesFromJSON($response); + + return $translation; + } + + /** + * Internal function to make the request to the translator service + * + * @internal + * + * @param string $source + * Original language taken from the 'translate' function + * @param string $target + * Target language taken from the ' translate' function + * @param string $text + * Text to translate taken from the 'translate' function + * + * @return object[] The response of the translation service in JSON format + */ + protected static function requestTranslation($source, $target, $text) + { + + // Google translate URL + $url = "https://translate.google.com/translate_a/single?client=at&dt=t&dt=ld&dt=qca&dt=rm&dt=bd&dj=1&hl=es-ES&ie=UTF-8&oe=UTF-8&inputm=2&otf=2&iid=1dd3b944-fa62-4b55-b330-74909a99969e"; + + $fields = array( + 'sl' => urlencode($source), + 'tl' => urlencode($target), + 'q' => urlencode($text) + ); + + $max = 9000; + if(strlen($fields['q']) >= $max) + throw new \Exception("Maximum number of characters exceeded: ".strlen($fields['q'])."/$max"); + + // URL-ify the data for the POST + $fields_string = ""; + foreach ($fields as $key => $value) { + $fields_string .= $key . '=' . $value . '&'; + } + + rtrim($fields_string, '&'); + + // Open connection + $ch = curl_init(); + + // Set the url, number of POST vars, POST data + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, count($fields)); + curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_ENCODING, 'UTF-8'); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_USERAGENT, 'AndroidTranslate/5.3.0.RC02.130475354-53000263 5.1 phone TRANSLATE_OPM5_TEST_1'); + + // Execute post + $result = curl_exec($ch); + + // Close connection + curl_close($ch); + + return $result; + } + + /** + * Dump of the JSON's response in an array + * + * @param string $json + * The JSON object returned by the request function + * + * @return string A single string with the translation + */ + protected static function getSentencesFromJSON($json) + { + $sentencesArray = json_decode($json, true); + $sentences = ""; + + foreach ($sentencesArray["sentences"] as $s) { + $sentences .= isset($s["trans"]) ? $s["trans"] : ''; + } + + return $sentences; + } +} \ No newline at end of file diff --git a/frontend/tools/translator/libs/translator.php b/frontend/tools/translator/libs/translator.php new file mode 100644 index 0000000..8e26704 --- /dev/null +++ b/frontend/tools/translator/libs/translator.php @@ -0,0 +1,416 @@ +isDir()) { + continue; + } + $this->files[] = $file->getPathname(); + } + } + else if (is_file($dir)) { + $this->files[] = $dir; + } + else { + throw new Exception('can not import object: ' . $dir); + } + } + } + + /** + * extracts strings from files + * + * @throws Exception + */ + public function extract() { + $this->strings = array(); + foreach ($this->files as $file) { + $content = file_get_contents($file); + if ($content == '') + throw new Exception('can not get file content: ' . $content); + + $strings = array(); + + //drop svg + $content = preg_replace('||i', '', $content); + + //drop exceptions + $content = preg_replace('|\/\/no-translate BEGIN[\s\S]+?\/\/no-translate END|mi', '', $content); + + //drop + $content = stripslashes($content); + + if (stripos($file, '.js') !== false) { + //json + + $ignore_matches = [ + '\.addEventListener', + '\.style\.', + 'aria-label', + '\.font', + 'Path2D\(', + ]; + foreach ($ignore_matches as $ignore_match) { + $content = preg_replace('/' . $ignore_match . '.*/', '', $content); + } + + $content = preg_replace('|[\r\n][ \t]*//.*|', "\n", $content); + + //extract between ' ' + $out = array(); + preg_match_all("/[']([^']*)[']/", $content, $out); + $strings = array_merge($strings, $out[1]); + + //extract between " " + $out = array(); + preg_match_all('/["]([^"]*)["]/', $content, $out); + $strings = array_merge($strings, $out[1]); + } + if (stripos($file, '.htm') !== false || true) { + //html + $ignore_attributes = [ + 'dir', + 'lang', + 'http-equiv', + 'content', + 'name', + 'rel', + 'style', + 'onclick', + 'type', + 'class', + 'id', + 'href', + 'onchange', + 'onKeyUp', + 'oninput', + 'src', + 'aria-label', + ]; + foreach ($ignore_attributes as $ignore_attribute) { + $content = preg_replace('/' . $ignore_attribute . '="[^"]*"/', '', $content); + } + + //extract between " " + $out = array(); + preg_match_all('/["]([^"]*)["]/', $content, $out); + //$strings = array_merge($strings, $out[1]); + //extract between > < + $out = array(); + preg_match_all('|>([^<]{1,200})<[^ ]|', $content, $out); + $strings = array_merge($strings, $out[1]); + } + + foreach ($strings as $string) { + if (trim($string) == '' || substr($string, 0, 2) == './') + continue; + + //remove tags + $string = preg_replace('/<[^>]*>/', ' ', $string); + $string = trim($string); + + $this->strings[] = $string; + } + } + + $this->strings = array_unique($this->strings); + sort($this->strings); + } + + /** + * filters out some strings + */ + public function filter() { + $copy = $this->strings; + $this->strings = array(); + foreach ($copy as $string) { + $string = trim($string); + if (is_numeric($string)) { + //number + continue; + } + if (strlen($string) < 2) { + //too short + continue; + } + if (preg_replace("/[^A-Z0-9]+/", "", $string[0]) == '') { + //first letter must be common uppercase letter or number + continue; + } + if (is_numeric($string[0]) && strpos($string, ' ') === false) { + //if first letter is number - try to skip some + continue; + } + if (strpos($string, '(') !== false && strpos($string, ')') !== false && strpos($string, '.') !== false) { + //function, not string + continue; + } + if (strpos($string, "\n") !== false || strpos($string, "\r") !== false) { + //multi-line + continue; + } + if (preg_replace("/[^a-z]+/", "", $string) == '') { + //all caps - not translatable + continue; + } + if (strlen($string) > 30 && strpos($string, " ") === false) { + //long word without spaces + continue; + } + + $this->strings[] = $string; + } + $this->strings = array_unique($this->strings); + $this->strings = array_values($this->strings); + } + + /** + * prepare strings for translating for user + * + * @throws Exception + */ + public function prepare() { + $this->scan(); + $this->extract(); + $this->filter(); + + $data = $this->strings; + + $in_content = ''; + if (isset($_POST['in'])) + $in_content = $_POST['in']; + + echo '

'; + echo 'Translate text above with translator and paste result below:

'; + echo '
'; + echo ''; + } + + /** + * combines source strings and manually translated strings to json format + * + * @param string $translation + * + * @throws Exception + */ + public function add_translation($translation) { + $translation = trim($translation); + if ($translation != '') + $translation = explode("\n", $translation); + else + $translation = array(); + + if (count($this->strings) == 0) + throw new Exception('0 translations found in files.'); + if (count($this->strings) != count($translation)) + throw new Exception(count($this->strings) . ' translations imported from file, but you provided ' . count($translation) . ', it must match'); + + $this->translations = new stdClass(); + foreach ($this->strings as $key => $value) { + $translated = trim($translation[$key]); + + $this->translations->$value = $translated; + } + } + + /** + * translates everything automatically + * + * @throws Exception + */ + public function auto_translate($action_string) { + global $LANGUAGES, $LANG_DIR; + + $action_string = str_replace('Auto Translate: ', '', $action_string); + if($action_string == 'all'){ + $action_string = ''; + } + + $service = new GoogleTranslate(); + $text = implode("\n", $this->strings); + + foreach ($LANGUAGES as $lang) { + if($action_string != '' && $action_string != $lang){ + continue; + } + + echo "
$lang: "; + + $file_path = $LANG_DIR . strtolower($lang) . ".json"; + + //read old translations + $old = array(); + if (file_exists($file_path)) { + $old = file_get_contents($file_path); + if ($old === false) + throw new Exception('can not open file: ' . $file_path); + $old = json_decode($old); + if ($old === null) + throw new Exception($file_path . ' data is not json'); + } + + $translation = $service->translate('en', $lang, $text); + if ($translation == '') { + throw new Exception('empty response from translation service'); + } + $translation = str_replace("\r", '', $translation); + $translation = explode("\n", $translation); + if (count($this->strings) != count($translation)) { + throw new Exception(count($this->strings) . ' translations imported from file, but service gave: ' . count($translation) . ', it must match'); + } + + //generate array + $this->translations = new stdClass(); + foreach ($this->strings as $key => $value) { + $translated = trim($translation[$key]); + + $this->translations->$value = $translated; + } + + //merge + $merged = (object) array_merge((array) $this->translations, (array) $old); + + //remove not use elements + foreach ($merged as $k => $v) { + if (isset($this->translations->$k) == false) { + $v = null; + unset($merged->$k); + } + } + $this->translations = $merged; + + //generate JSON + $html = json_encode($this->translations, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + //save + $written = file_put_contents($file_path, $html); + if ($written == 0) { + throw new Exception('can not write to: ' . $file_path); + } + else { + echo 'OK'; + } + + //sleep 05-1s + usleep(rand(500, 1000) * 1000); + } + } + + /** + * saves current data as empty file + * + * @throws Exception + */ + public function save_empty() { + global $LANG_DIR_EMPTY; + + if ($LANG_DIR_EMPTY == '') + return; + + $data = new stdClass(); + foreach($this->strings as $value){ + $data->$value = ''; + } + + $data_encoded = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + $written = file_put_contents($LANG_DIR_EMPTY, $data_encoded); + if ($written == 0) { + throw new Exception('can not write to: ' . $LANG_DIR_EMPTY); + } + else { + echo '

File updated: ' . $LANG_DIR_EMPTY . '

'; + } + } + + /** + * show formatted translation, use json. parameters are only for testing mode + */ + public function show_merged() { + echo ''; + } + + /** + * merge two translations + * + * @throws Exception + */ + public function merge() { + echo 'Old translations: (priority on same keys)
'; + $value = ''; + if (isset($_POST['merge_old'])) + $value = $_POST['merge_old']; + echo ''; + + echo '

'; + + echo 'New translations:
'; + $value = ''; + if (isset($_POST['merge_new'])) + $value = $_POST['merge_new']; + echo ''; + echo '

'; + + if (isset($_POST['merge_old']) == false) + return; + + $old = json_decode($_POST['merge_old']); + $new = json_decode($_POST['merge_new']); + + if ($old === null) + throw new Exception('Old data is not json'); + if ($new === null) + throw new Exception('New data is not json'); + + //merge + $merged = (object) array_merge((array) $new, (array) $old); + + //remove not use elements + foreach ($merged as $k => $v) { + if (isset($new->$k) == false) { + $v = null; + unset($merged->$k); + } + } + + echo ''; + } + +} diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js new file mode 100644 index 0000000..30ba843 --- /dev/null +++ b/frontend/webpack.config.js @@ -0,0 +1,56 @@ +var webpack = require('webpack'); +var path = require('path'); + +module.exports = { + entry: [ + './src/js/main.js', + ], + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'bundle.js', + publicPath: '/dist/' + }, + resolve: { + extensions: ['.js', '.css'], + alias: { + Utilities: path.resolve(__dirname, './../node_modules/') + } + }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: {url: false} + } + ] + }, + { + test: /\.js$/, + exclude: /(node_modules|bower_components)/, + use: ['babel-loader'] + }, + ] + }, + plugins: [ + new webpack.ProvidePlugin({ + $: "jquery", + jQuery: "jquery", + "window.jQuery": "jquery" + }), + new webpack.DefinePlugin({ + VERSION: JSON.stringify(require("./package.json").version) + }), + ], + devtool: "cheap-module-source-map", + devServer: { + // host: '0.0.0.0', + //contentBase: "./", + static: { + directory: path.resolve(__dirname, "./"), + }, + } +}; \ No newline at end of file